diff --git a/app/assets/images/pages/play/modal/game-menu-background.png b/app/assets/images/pages/play/modal/game-menu-background.png index 5296ff060..aa7a5ec28 100644 Binary files a/app/assets/images/pages/play/modal/game-menu-background.png and b/app/assets/images/pages/play/modal/game-menu-background.png differ diff --git a/app/assets/images/pages/play/modal/k-means.gif b/app/assets/images/pages/play/modal/k-means.gif new file mode 100644 index 000000000..862df3aa1 Binary files /dev/null and b/app/assets/images/pages/play/modal/k-means.gif differ diff --git a/app/lib/Angel.coffee b/app/lib/Angel.coffee index e29293dcb..1c3137e85 100644 --- a/app/lib/Angel.coffee +++ b/app/lib/Angel.coffee @@ -130,7 +130,7 @@ module.exports = class Angel extends CocoClass eventType = if finished then 'god:new-world-created' else 'god:streaming-world-updated' if finished @shared.world = world - Backbone.Mediator.publish eventType, world: world, firstWorld: @shared.firstWorld, goalStates: goalStates, team: me.team, firstChangedFrame: firstChangedFrame + Backbone.Mediator.publish eventType, world: world, firstWorld: @shared.firstWorld, goalStates: goalStates, team: me.team, firstChangedFrame: firstChangedFrame, finished: finished if finished for scriptNote in @shared.world.scriptNotes Backbone.Mediator.publish scriptNote.channel, scriptNote.event diff --git a/app/lib/AudioPlayer.coffee b/app/lib/AudioPlayer.coffee index 9c5588d8f..eec0deb48 100644 --- a/app/lib/AudioPlayer.coffee +++ b/app/lib/AudioPlayer.coffee @@ -123,7 +123,7 @@ class AudioPlayer extends CocoClass return if filename of cache name ?= filename # SoundJS flips out if you try to register the same file twice - createjs.Sound.registerSound(filename, name, 1, true) # 1: 1 channel, true: should preload + result = createjs.Sound.registerSound(filename, name, 1) # 1: 1 channel cache[filename] = new Media(name) # PROGRESS CALLBACKS diff --git a/app/lib/GPlusHandler.coffee b/app/lib/GPlusHandler.coffee index e69c7db79..411b1e26b 100644 --- a/app/lib/GPlusHandler.coffee +++ b/app/lib/GPlusHandler.coffee @@ -91,7 +91,9 @@ module.exports = GPlusHandler = class GPlusHandler extends CocoClass @saveIfAllDone() saveIfAllDone: => + console.debug 'Save if all done. Person loaded:', @personLoaded, 'and email loaded:', @emailLoaded return unless @personLoaded and @emailLoaded + console.debug 'Email, gplusID:', me.get('email'), me.get('gplusID') return unless me.get('email') and me.get('gplusID') Backbone.Mediator.publish 'auth:logging-in-with-gplus', {} @@ -104,12 +106,16 @@ module.exports = GPlusHandler = class GPlusHandler extends CocoClass patch.email = me.get('email') wasAnonymous = me.get('anonymous') @trigger 'logging-into-codecombat' + console.debug('Logging into GPlus.') me.save(patch, { patch: true type: 'PUT' - error: backboneFailure, + error: -> + console.debug('Logging into GPlus fail.', arguments) + backboneFailure(arguments...) url: "/db/user?gplusID=#{gplusID}&gplusAccessToken=#{@accessToken.access_token}" success: (model) -> + console.debug('GPLus login success!') window.location.reload() if wasAnonymous and not model.get('anonymous') }) diff --git a/app/lib/LevelBus.coffee b/app/lib/LevelBus.coffee index 33595c251..04a72d530 100644 --- a/app/lib/LevelBus.coffee +++ b/app/lib/LevelBus.coffee @@ -211,10 +211,12 @@ module.exports = class LevelBus extends Bus @changedSessionProperties.state = true @reallySaveSession() # Make sure it saves right away; don't debounce it. - onNewGoalStates: ({goalStates}) -> + onNewGoalStates: (e) -> # TODO: this log doesn't capture when null-status goals are being set during world streaming. Where can they be coming from? + goalStates = e.goalStates return console.error("Somehow trying to save null goal states!", newGoalStates) if _.find(newGoalStates, (gs) -> not gs.status) + return unless e.overallStatus is 'success' newGoalStates = goalStates state = @session.get('state') oldGoalStates = state.goalStates or {} diff --git a/app/lib/LevelOptions.coffee b/app/lib/LevelOptions.coffee index 4470c4ebe..6e036418a 100644 --- a/app/lib/LevelOptions.coffee +++ b/app/lib/LevelOptions.coffee @@ -1,5 +1,6 @@ module.exports = LevelOptions = 'dungeons-of-kithgard': + disableSpaces: true hidesSubmitUntilRun: true hidesPlayButton: true hidesRunShortcut: true @@ -9,7 +10,9 @@ module.exports = LevelOptions = hidesRealTimePlayback: true requiredGear: {feet: 'simple-boots'} restrictedGear: {feet: 'leather-boots'} + requiredCode: ['moveRight'] 'gems-in-the-deep': + disableSpaces: true hidesSubmitUntilRun: true hidesPlayButton: true hidesRunShortcut: true @@ -20,6 +23,7 @@ module.exports = LevelOptions = requiredGear: {feet: 'simple-boots'} restrictedGear: {feet: 'leather-boots'} 'shadow-guard': + disableSpaces: true hidesSubmitUntilRun: true hidesPlayButton: true hidesRunShortcut: true @@ -30,6 +34,7 @@ module.exports = LevelOptions = requiredGear: {feet: 'simple-boots'} restrictedGear: {feet: 'leather-boots', 'right-hand': 'simple-sword'} 'kounter-kithwise': + disableSpaces: true hidesPlayButton: true hidesRunShortcut: true hidesHUD: true @@ -48,6 +53,7 @@ module.exports = LevelOptions = requiredGear: {feet: 'simple-boots'} restrictedGear: {feet: 'leather-boots', 'right-hand': 'simple-sword', 'programming-book': 'programmaticon-i'} 'forgetful-gemsmith': + disableSpaces: true hidesPlayButton: true hidesRunShortcut: true hidesHUD: true @@ -57,6 +63,7 @@ module.exports = LevelOptions = requiredGear: {feet: 'simple-boots'} restrictedGear: {feet: 'leather-boots', 'programming-book': 'programmaticon-i'} 'true-names': + disableSpaces: true hidesPlayButton: true hidesRunShortcut: true hidesHUD: true @@ -65,7 +72,10 @@ module.exports = LevelOptions = hidesRealTimePlayback: true requiredGear: {feet: 'simple-boots', 'right-hand': 'simple-sword', waist: 'leather-belt'} restrictedGear: {feet: 'leather-boots'} + requiredCode: ['Brak'] 'favorable-odds': + disableSpaces: true + hidesPlayButton: true hidesRunShortcut: true hidesHUD: true hidesSay: true @@ -74,6 +84,8 @@ module.exports = LevelOptions = requiredGear: {feet: 'simple-boots', 'right-hand': 'simple-sword'} restrictedGear: {feet: 'leather-boots'} 'the-raised-sword': + disableSpaces: true + hidesPlayButton: true hidesRunShortcut: true hidesHUD: true hidesSay: true @@ -89,6 +101,7 @@ module.exports = LevelOptions = hidesRealTimePlayback: true requiredGear: {feet: 'simple-boots', 'programming-book': 'programmaticon-i'} restrictedGear: {feet: 'leather-boots'} + requiredCode: ['loop'] 'haunted-kithmaze': hidesRunShortcut: true hidesHUD: true @@ -97,6 +110,7 @@ module.exports = LevelOptions = hidesRealTimePlayback: true requiredGear: {feet: 'simple-boots', 'programming-book': 'programmaticon-i'} restrictedGear: {feet: 'leather-boots'} + requiredCode: ['loop'] 'descending-further': hidesHUD: true hidesSay: true @@ -132,6 +146,8 @@ module.exports = LevelOptions = hidesRealTimePlayback: true requiredGear: {feet: 'simple-boots', 'right-hand': 'simple-sword', 'programming-book': 'programmaticon-i', eyes: 'crude-glasses', torso: 'leather-tunic'} restrictedGear: {feet: 'leather-boots'} + requiredCode: ['findNearestEnemy'] + suspectCode: [{name: 'lone-find-nearest-enemy', pattern: /^[ ]*(self|this|@)?[:.]?findNearestEnemy()/m}] 'lowly-kithmen': hidesHUD: true hidesSay: true @@ -139,6 +155,8 @@ module.exports = LevelOptions = hidesRealTimePlayback: true requiredGear: {feet: 'simple-boots', 'right-hand': 'simple-sword', 'programming-book': 'programmaticon-i', eyes: 'crude-glasses', torso: 'leather-tunic'} restrictedGear: {feet: 'leather-boots'} + requiredCode: ['findNearestEnemy'] + suspectCode: [{name: 'lone-find-nearest-enemy', pattern: /^[ ]*(self|this|@)?[:.]?findNearestEnemy()/m}] 'closing-the-distance': hidesHUD: true hidesSay: true @@ -146,6 +164,7 @@ module.exports = LevelOptions = hidesRealTimePlayback: true requiredGear: {feet: 'simple-boots', 'right-hand': 'simple-sword', torso: 'leather-tunic', eyes: 'crude-glasses'} restrictedGear: {feet: 'leather-boots'} + suspectCode: [{name: 'lone-find-nearest-enemy', pattern: /^[ ]*(self|this|@)?[:.]?findNearestEnemy()/m}] 'tactical-strike': hidesHUD: true hidesSay: true @@ -153,12 +172,14 @@ module.exports = LevelOptions = hidesRealTimePlayback: true requiredGear: {feet: 'simple-boots', 'right-hand': 'simple-sword', torso: 'leather-tunic', eyes: 'crude-glasses'} restrictedGear: {feet: 'leather-boots'} + suspectCode: [{name: 'lone-find-nearest-enemy', pattern: /^[ ]*(self|this|@)?[:.]?findNearestEnemy()/m}] 'the-final-kithmaze': hidesHUD: true hidesSay: true hidesCodeToolbar: true hidesRealTimePlayback: true requiredGear: {feet: 'simple-boots', 'right-hand': 'simple-sword', torso: 'leather-tunic', 'programming-book': 'programmaticon-i', eyes: 'crude-glasses'} + suspectCode: [{name: 'lone-find-nearest-enemy', pattern: /^[ ]*(self|this|@)?[:.]?findNearestEnemy()/m}] 'the-gauntlet': hidesHUD: true hidesSay: true @@ -166,6 +187,7 @@ module.exports = LevelOptions = hidesRealTimePlayback: true requiredGear: {feet: 'simple-boots', 'right-hand': 'simple-sword', torso: 'leather-tunic', 'programming-book': 'programmaticon-i', eyes: 'crude-glasses'} restrictedGear: {feet: 'leather-boots'} + suspectCode: [{name: 'lone-find-nearest-enemy', pattern: /^[ ]*(self|this|@)?[:.]?findNearestEnemy()/m}] 'kithgard-gates': hidesSay: true hidesCodeToolbar: true diff --git a/app/lib/surface/CoordinateDisplay.coffee b/app/lib/surface/CoordinateDisplay.coffee index e9928ed60..5f4684884 100644 --- a/app/lib/surface/CoordinateDisplay.coffee +++ b/app/lib/surface/CoordinateDisplay.coffee @@ -6,6 +6,7 @@ module.exports = class CoordinateDisplay extends createjs.Container 'surface:mouse-over': 'onMouseOver' 'surface:stage-mouse-down': 'onMouseDown' 'camera:zoom-updated': 'onZoomUpdated' + 'level:flag-color-selected': 'onFlagColorSelected' constructor: (options) -> super() @@ -60,6 +61,9 @@ module.exports = class CoordinateDisplay extends createjs.Container @hide() @show() + onFlagColorSelected: (e) -> + @placingFlag = Boolean e.color + hide: -> return unless @label.parent @removeChild @label @@ -154,6 +158,6 @@ module.exports = class CoordinateDisplay extends createjs.Container @y = sup.y @addChild @background @addChild @label - @addChild @pointMarker + @addChild @pointMarker unless @placingFlag @updateCache() Backbone.Mediator.publish 'surface:coordinates-shown', {} diff --git a/app/lib/surface/Lank.coffee b/app/lib/surface/Lank.coffee index 731119d1c..16d76032d 100644 --- a/app/lib/surface/Lank.coffee +++ b/app/lib/surface/Lank.coffee @@ -491,6 +491,7 @@ module.exports = Lank = class Lank extends CocoClass bar = createProgressBar(healthColor) @options.floatingLayer.addCustomGraphic(key, bar, bar.bounds) + hadHealthBar = @healthBar @healthBar = new createjs.Sprite(@options.floatingLayer.spriteSheet) @healthBar.gotoAndStop(key) offset = @getOffset 'aboveHead' @@ -499,6 +500,8 @@ module.exports = Lank = class Lank extends CocoClass @options.floatingLayer.addChild @healthBar @updateHealthBar() @lastHealth = null + if not hadHealthBar + @listenTo @options.floatingLayer, 'new-spritesheet', @addHealthBar getActionProp: (prop, subProp, def=null) -> # Get a property or sub-property from an action, falling back to ThangType @@ -664,7 +667,9 @@ module.exports = Lank = class Lank extends CocoClass updateLabels: -> return unless @thang blurb = if @thang.health <= 0 then null else @thang.sayMessage # Dead men tell no tales - @addLabel 'say', Label.STYLE_SAY if blurb + blurb = null if blurb in ['For Thoktar!', 'Bones!', 'Behead!', 'Destroy!', 'Die, humans!'] # Let's just hear, not see, these ones. + labelStyle = if /Hero Placeholder/.test(@thang.id) then Label.STYLE_DIALOGUE else Label.STYLE_SAY + @addLabel 'say', labelStyle if blurb if @labels.say?.setText blurb @notifySpeechUpdated blurb: blurb label.update() for name, label of @labels diff --git a/app/lib/surface/SegmentedSprite.coffee b/app/lib/surface/SegmentedSprite.coffee index 45f03e133..ad280910e 100644 --- a/app/lib/surface/SegmentedSprite.coffee +++ b/app/lib/surface/SegmentedSprite.coffee @@ -13,7 +13,7 @@ module.exports = class SegmentedSprite extends createjs.SpriteContainer constructor: (@spriteSheet, @thangType, @spriteSheetPrefix, @resolutionFactor=SPRITE_RESOLUTION_FACTOR) -> @spriteSheet.mcPool ?= {} - @initialize(@spriteSheet) + super(@spriteSheet) @addEventListener 'tick', @handleTick destroy: -> diff --git a/app/lib/surface/SingularSprite.coffee b/app/lib/surface/SingularSprite.coffee index 46cd04bbe..9a7c93e2a 100644 --- a/app/lib/surface/SingularSprite.coffee +++ b/app/lib/surface/SingularSprite.coffee @@ -6,7 +6,7 @@ module.exports = class SingularSprite extends createjs.Sprite childMovieClips: null constructor: (@spriteSheet, @thangType, @spriteSheetPrefix, @resolutionFactor=SPRITE_RESOLUTION_FACTOR) -> - @initialize(@spriteSheet) + super(@spriteSheet) destroy: -> @removeAllEventListeners() diff --git a/app/lib/surface/Surface.coffee b/app/lib/surface/Surface.coffee index 20c76ad36..016fc81d6 100644 --- a/app/lib/surface/Surface.coffee +++ b/app/lib/surface/Surface.coffee @@ -1,5 +1,5 @@ CocoClass = require 'lib/CocoClass' -path = require './path' +TrailMaster = require './TrailMaster' Dropper = require './Dropper' AudioPlayer = require 'lib/AudioPlayer' {me} = require 'lib/auth' @@ -181,7 +181,6 @@ module.exports = Surface = class Surface extends CocoClass framesDropped = 0 while true Dropper.tick() - @trailmaster.tick() if @trailmaster # Skip some frame updates unless we're playing and not at end (or we haven't drawn much yet) frameAdvanced = (@playing and @currentFrame < lastFrame) or @totalFramesDrawn < 2 if frameAdvanced and @playing @@ -210,7 +209,6 @@ module.exports = Surface = class Surface extends CocoClass @updateState @currentFrame isnt oldFrame @drawCurrentFrame e @onFrameChanged() - @updatePaths() if (@totalFramesDrawn % 4) is 0 or createjs.Ticker.getMeasuredFPS() > createjs.Ticker.getFPS() - 5 Backbone.Mediator.publish('surface:ticked', {dt: 1 / @options.frameRate}) mib = @webGLStage.mouseInBounds if @mouseInBounds isnt mib @@ -260,7 +258,9 @@ module.exports = Surface = class Surface extends CocoClass createjs.Tween.removeTweens(@) @currentFrame = @scrubbingTo - @scrubbingTo = Math.min(Math.round(progress * (@world.frames.length - 1)), @world.frames.length - 1) + @scrubbingTo = Math.round(progress * (@world.frames.length - 1)) + @scrubbingTo = Math.max @scrubbingTo, 1 + @scrubbingTo = Math.min @scrubbingTo, @world.frames.length - 1 @scrubbingPlaybackSpeed = Math.sqrt(Math.abs(@scrubbingTo - @currentFrame) * @world.dt / (scrubDuration or 0.5)) if scrubDuration t = createjs.Tween @@ -314,10 +314,12 @@ module.exports = Surface = class Surface extends CocoClass if paused @surfacePauseTimeout = _.delay performToggle, 2000 @lankBoss.stop() + @trailmaster?.stop() @playbackOverScreen.show() else performToggle() @lankBoss.play() + @trailmaster?.play() @playbackOverScreen.hide() @@ -402,7 +404,7 @@ module.exports = Surface = class Surface extends CocoClass @playing = (e ? {}).playing ? true @setPlayingCalled = true if @playing and @currentFrame >= (@world.totalFrames - 5) - @currentFrame = 0 + @currentFrame = 1 # Go back to the beginning (but not frame 0, that frame is weird) if @fastForwardingToFrame and not @playing @fastForwardingToFrame = null @@ -456,7 +458,11 @@ module.exports = Surface = class Surface extends CocoClass @fastForwardingSpeed = lag / intendedLag else @fastForwardingToFrame = @fastForwardingSpeed = null - #console.log "on new world, lag", lag, "intended lag", intendedLag, "fastForwardingToFrame", @fastForwardingToFrame, "speed", @fastForwardingSpeed, "cause we are at", @world.age, "of", @world.frames.length * @world.dt +# console.log "on new world, lag", lag, "intended lag", intendedLag, "fastForwardingToFrame", @fastForwardingToFrame, "speed", @fastForwardingSpeed, "cause we are at", @world.age, "of", @world.frames.length * @world.dt + if event.finished + @updatePaths() + else + @hidePaths() onIdleChanged: (e) -> @setPaused e.idle unless @ended @@ -540,10 +546,12 @@ module.exports = Surface = class Surface extends CocoClass #- Camera focus on hero focusOnHero: -> + hadHero = @heroLank @heroLank = @lankBoss.lankFor 'Hero Placeholder' if me.team is 'ogres' # TODO: do this for real @heroLank = @lankBoss.lankFor 'Hero Placeholder 1' + @updatePaths() if not hadHero #- Real-time playback @@ -576,22 +584,16 @@ module.exports = Surface = class Surface extends CocoClass - #- Paths - TODO: move to LankBoss? but only update on frame drawing instead of on every frame update? - updatePaths: -> - return # TODO: Get paths working again with WebGL - return unless @options.paths - return if @casting + return unless @options.paths and @heroLank + return unless me.isAdmin() # TODO: Fix world thang points, targets, then remove this @hidePaths() - selectedThang = @lankBoss.selectedLank?.thang return if @world.showPaths is 'never' - return if @world.showPaths is 'paused' and @playing - return if @world.showPaths is 'selected' and not selectedThang - @trailmaster ?= new path.Trailmaster @camera - selectedOnly = @playing and @world.showPaths is 'selected' - @paths = @trailmaster.generatePaths @world, @getCurrentFrame(), selectedThang, @lankBoss.lanks, selectedOnly + layerAdapter = @lankBoss.layerAdapters['Path'] + @trailmaster ?= new TrailMaster @camera, layerAdapter + @paths = @trailmaster.generatePaths @world, @heroLank.thang @paths.name = 'paths' - @lankBoss.layerAdapters['Path'].addChild @paths + layerAdapter.addChild @paths hidePaths: -> return if not @paths @@ -699,6 +701,7 @@ module.exports = Surface = class Surface extends CocoClass @normalStage.clear() @webGLStage.clear() @musicPlayer?.destroy() + @trailmaster?.destroy() @normalStage.removeAllChildren() @webGLStage.removeAllChildren() @webGLStage.removeEventListener 'stagemousemove', @onMouseMove diff --git a/app/lib/surface/TrailMaster.coffee b/app/lib/surface/TrailMaster.coffee new file mode 100644 index 000000000..80462d902 --- /dev/null +++ b/app/lib/surface/TrailMaster.coffee @@ -0,0 +1,122 @@ +PAST_PATH_ALPHA = 0.75 +PAST_PATH_WIDTH = 5 +FUTURE_PATH_ALPHA = 0.75 +FUTURE_PATH_WIDTH = 4 +TARGET_ALPHA = 1 +TARGET_WIDTH = 10 +FUTURE_PATH_INTERVAL_DIVISOR = 4 +PAST_PATH_INTERVAL_DIVISOR = 2 + +Camera = require './Camera' +CocoClass = require 'lib/CocoClass' + +module.exports = class TrailMaster extends CocoClass + world: null + + constructor: (@camera, @layerAdapter) -> + super() + @tweenedSprites = [] + @tweens = [] + @listenTo @layerAdapter, 'new-spritesheet', -> @generatePaths(@world, @thang) + + generatePaths: (@world, @thang) -> + return if @generatingPaths + @generatingPaths = true + @cleanUp() + @createGraphics() + pathDisplayObject = new createjs.SpriteContainer(@layerAdapter.spriteSheet) + pathDisplayObject.mouseEnabled = pathDisplayObject.mouseChildren = false + pathDisplayObject.addChild @createFuturePath() +# pathDisplayObject.addChild @createPastPath() # Just made the animated path the full path... do we want to have past and future look different again? + pathDisplayObject.addChild @createTargets() + @generatingPaths = false + return pathDisplayObject + + cleanUp: -> + createjs.Tween.removeTweens(sprite) for sprite in @tweenedSprites + @tweenedSprites = [] + @tweens = [] + + createGraphics: -> + @targetDotKey = @cachePathDot(TARGET_WIDTH, @colorForThang(@thang.team, TARGET_ALPHA), [0, 0, 0, 1]) + @pastDotKey = @cachePathDot(PAST_PATH_WIDTH, @colorForThang(@thang.team, PAST_PATH_ALPHA), [0, 0, 0, 1]) + @futureDotKey = @cachePathDot(FUTURE_PATH_WIDTH, [255, 255, 255, FUTURE_PATH_ALPHA], @colorForThang(@thang.team, 1)) + + cachePathDot: (width, fillColor, strokeColor) -> + key = "path-dot-#{width}-#{fillColor}-#{strokeColor}" + fillColor = createjs.Graphics.getRGB(fillColor...) + strokeColor = createjs.Graphics.getRGB(strokeColor...) + unless key in @layerAdapter.spriteSheet.getAnimations() + circle = new createjs.Shape() + radius = width/2 + circle.graphics.setStrokeStyle(width/5).beginFill(fillColor).beginStroke(strokeColor).drawCircle(0, 0, radius) + @layerAdapter.addCustomGraphic(key, circle, [-radius*1.5, -radius*1.5, radius*3, radius*3]) + return key + + colorForThang: (team, alpha=1.0) -> + rgb = [0, 255, 0] + rgb = [255, 0, 0] if team is 'humans' + rgb = [0, 0, 255] if team is 'ogres' + rgb.push(alpha) + return rgb + + createPastPath: -> + return unless points = @world.pointsForThang @thang.id, @camera + interval = Math.max(1, parseInt(@world.frameRate / PAST_PATH_INTERVAL_DIVISOR)) + params = { interval: interval, frameKey: @pastDotKey } + return @createPath(points, params) + + createFuturePath: -> + return unless points = @world.pointsForThang @thang.id, @camera + interval = Math.max(1, parseInt(@world.frameRate / FUTURE_PATH_INTERVAL_DIVISOR)) + params = { interval: interval, animate: true, frameKey: @futureDotKey } + return @createPath(points, params) + + createTargets: -> + return unless @thang.allTargets + container = new createjs.SpriteContainer(@layerAdapter.spriteSheet) + for x, i in @thang.allTargets by 2 + y = @thang.allTargets[i + 1] + sup = @camera.worldToSurface x: x, y: y + sprite = new createjs.Sprite(@layerAdapter.spriteSheet) + sprite.scaleX = sprite.scaleY = 1 / @layerAdapter.resolutionFactor + sprite.scaleY *= @camera.y2x + sprite.gotoAndStop(@targetDotKey) + sprite.x = sup.x + sprite.y = sup.y + container.addChild(sprite) + return container + + createPath: (points, options={}) -> + options = options or {} + interval = options.interval or 8 + key = options.frameKey or @pastDotKey + container = new createjs.SpriteContainer(@layerAdapter.spriteSheet) + + for x, i in points by interval * 2 + y = points[i + 1] + sprite = new createjs.Sprite(@layerAdapter.spriteSheet) + sprite.scaleX = sprite.scaleY = 1 / @layerAdapter.resolutionFactor + sprite.scaleY *= @camera.y2x + sprite.gotoAndStop(key) + sprite.x = x + sprite.y = y + container.addChild(sprite) + if lastSprite and options.animate + tween = createjs.Tween.get(lastSprite, {loop: true}).to({x:x, y:y}, 1000) + @tweenedSprites.push lastSprite + @tweens.push tween + lastSprite = sprite + + @logged = true + container + + play: -> + tween.setPaused(false) for tween in @tweens + + stop: -> + tween.setPaused(true) for tween in @tweens + + destroy: -> + @cleanUp() + super() diff --git a/app/lib/surface/path.coffee b/app/lib/surface/path.coffee deleted file mode 100644 index 149d4efbc..000000000 --- a/app/lib/surface/path.coffee +++ /dev/null @@ -1,289 +0,0 @@ -# paths before the current state taper out, -# and have a different color than the future -PAST_PATH_TAIL_BRIGHTNESS = 150 -PAST_PATH_TAIL_ALPHA = 0.3 -PAST_PATH_HEAD_BRIGHTNESS = 200 -PAST_PATH_HEAD_ALPHA = 0.75 - -PAST_PATH_HEAD_LENGTH = 50 -PAST_PATH_TAIL_WIDTH = 2 -PAST_PATH_HEAD_WIDTH = 2 -PAST_PATH_MAX_LENGTH = 200 - -# paths in the future are single color dotted lines -FUT_PATH_BRIGHTNESS = 153 -FUT_PATH_ALPHA = 0.8 -FUT_PATH_HEAD_LENGTH = 0 -FUT_PATH_WIDTH = 1 -FUT_PATH_MAX_LENGTH = 2000 - -# selected paths are single color, and larger, more prominent -# most other properties are the same as non-selected -SELECTED_PATH_TAIL_BRIGHTNESS = 146 -SELECTED_PATH_TAIL_ALPHA = 0.5 -SELECTED_PATH_HEAD_BRIGHTNESS = 200 -SELECTED_PATH_HEAD_ALPHA = 1.0 -SELECTED_PAST_PATH_MAX_LENGTH = 2000 - -FUT_SELECTED_PATH_WIDTH = 3 - -# for sprites along the path -CLONE_INTERVAL = 250 # distance between them, ignored for new actions -CLONE_SCALE = 1.0 -CLONE_ALPHA = 0.4 - -# path defaults -PATH_DOT_LENGTH = 3 -PATH_SEGMENT_LENGTH = 15 # should be > PATH_DOT_LENGTH - -Camera = require './Camera' - -module.exports.Trailmaster = class Trailmaster - paths: null # dictionary of thang ids to containers for their paths - selectedPath: null # container of path selected - pathDisplayObject: null - world: null - clock: 0 - - constructor: (@camera) -> - - tick: -> - @clock += 1 - - generatePaths: (@world, @currentFrame, @selectedThang, @sprites, @selectedOnly) -> - @paths = {} - @pathDisplayObject = new createjs.Container() - @pathDisplayObject.mouseEnabled = @pathDisplayObject.mouseChildren = false - for thang in world.thangs - continue unless thang.isSelectable - continue unless thang.isMovable - continue if @selectedOnly and thang isnt @selectedThang - path = @createPathForThang thang - continue if not path - @pathDisplayObject.addChild path - @paths[thang.id] = path - @pathDisplayObject - - createPathForThang: (thang) -> - container = new createjs.Container() - - path = @createPastPathForThang(thang) - container.addChild(path) if path - - path = @createFuturePathForThang(thang) - container.addChild(path) if path - - targets = @createTargetsForThang(thang) - container.addChild(targets) if targets - - if thang is @selectedThang - sprites = @spritesForThang(thang) - for sprite in sprites - container.addChild(sprite) - - container - - createPastPathForThang: (thang) -> - maxLength = if thang is @selectedThang then SELECTED_PAST_PATH_MAX_LENGTH else PAST_PATH_MAX_LENGTH - start = Math.max(@currentFrame - maxLength, 0) - start = 0 if thang isnt @selectedThang - resolution = if thang is @selectedThang then 4 else 12 - return unless points = @world.pointsForThang thang.id, start, @currentFrame, @camera, resolution - params = - tailWidth: PAST_PATH_TAIL_WIDTH - headWidth: PAST_PATH_HEAD_WIDTH - headLength: PAST_PATH_HEAD_LENGTH - if thang is @selectedThang - params['tailColor'] = colorForThang(thang.team, SELECTED_PATH_TAIL_BRIGHTNESS, SELECTED_PATH_TAIL_ALPHA) - params['headColor'] = colorForThang(thang.team, SELECTED_PATH_HEAD_BRIGHTNESS, SELECTED_PATH_HEAD_ALPHA) - else - params['tailColor'] = colorForThang(thang.team, PAST_PATH_TAIL_BRIGHTNESS, PAST_PATH_TAIL_ALPHA) - params['headColor'] = colorForThang(thang.team, PAST_PATH_HEAD_BRIGHTNESS, PAST_PATH_HEAD_ALPHA) - return createPath(points, params) - - - createFuturePathForThang: (thang) -> - resolution = 8 - return unless points = @world.pointsForThang thang.id, @currentFrame, @currentFrame + FUT_PATH_MAX_LENGTH, @camera, resolution - if thang is @selectedThang - color = colorForThang(thang.team, SELECTED_PATH_HEAD_BRIGHTNESS, SELECTED_PATH_HEAD_ALPHA) - else - color = colorForThang(thang.team, FUT_PATH_BRIGHTNESS, FUT_PATH_ALPHA) - return createPath(points, - tailColor: color - tailWidth: if thang is @selectedThang then FUT_SELECTED_PATH_WIDTH else FUT_PATH_WIDTH - headLength: FUT_PATH_HEAD_LENGTH - dotted: true - dotOffset: @clock - ) - - createTargetsForThang: (thang) -> - return unless thang.allTargets - g = new createjs.Graphics() - g.setStrokeStyle(0.5) - g.beginStroke(createjs.Graphics.getRGB(0, 0, 0)) - color = colorForThang(thang.team) - - i = 0 - while i < thang.allTargets.length - g.beginStroke(createjs.Graphics.getRGB(0, 0, 0)) - g.beginFill(createjs.Graphics.getRGB(color...)) - sup = @camera.worldToSurface x: thang.allTargets[i], y: thang.allTargets[i + 1] - g.drawEllipse(sup.x - 5, sup.y - 3, 10, 6) - g.endStroke() - - i += 2 - - s = new createjs.Shape(g) - s.x = 0 - s.y = 0 - s - - spritesForThang: (thang) -> - i = 0 - sprites = [] - sprite = @sprites[thang.id] - return sprites unless sprite?.actions - lastPos = @camera.surfaceToWorld x: sprite.sprite.x, y: sprite.sprite.y - minDistance = Math.pow(CLONE_INTERVAL * Camera.MPP, 2) - actions = @world.actionsForThang(thang.id) - lastAction = null - - for action in actions - continue if action.name in ['idle', 'move'] - frame = @world.frames[action.frame] - frame.restoreStateForThang(thang) - - if lastPos - diff = Math.pow(lastPos.x - thang.pos.x, 2) - diff += Math.pow(lastPos.y - thang.pos.y, 2) - continue if diff < minDistance and action.name is lastAction - - clone = sprite.sprite.clone() - clonePos = @camera.worldToSurface thang.pos - clone.x = clonePos.x - clone.y = clonePos.y - clone.alpha = CLONE_ALPHA - clone.scaleX *= CLONE_SCALE - clone.scaleY *= CLONE_SCALE - if sprite.expandActions # old Sprite - sprite.updateRotation(clone, sprite.data) - animActions = sprite.expandActions(if thang.acts then thang.getActionName() else 'idle') - sprite.applyActionsToSprites(animActions, [clone], true) - animation = clone.spriteSheet.getAnimation(clone.currentAnimation) - clone.currentAnimationFrame = Math.min(@clock % (animation.frames.length * 3), animation.frames.length - 1) - else - continue unless animation = sprite.actions[action.name] - sprite.updateRotation clone - animation = sprite.getActionDirection(animation) ? animation # no idea if this ever works - clone.gotoAndStop animation.name - # TODO: use action-specific framerate here? -# clone.currentAnimationFrame = Math.min(@clock % (animation.frames.length * 3), animation.frames.length - 1) - sprites.push(clone) - lastPos = x: thang.pos.x, y: thang.pos.y - lastAction = action.name - - @world.frames[@currentFrame].restoreStateForThang(thang) - sprites - -createPath = (points, options={}, g=null) -> - options = options or {} - tailColor = options.tailColor ? options.headColor - headColor = options.headColor ? options.tailColor - oneColor = true - oneColor = oneColor and headColor[i] is tailColor[i] for i in [0..4] - maxLength = options.maxLength or 0 - tailWidth = options.tailWidth - headWidth = options.headWidth - oneWidth = headWidth is tailWidth - headLength = options.headLength - dotted = options.dotted - dotOffset = if options.dotOffset? then options.dotOffset else 0 - - points = points.slice(-maxLength * 2) if maxLength isnt 0 - points = points.slice(((points.length / 2 + dotOffset) % PATH_SEGMENT_LENGTH) * 2) if dotOffset - g = new createjs.Graphics() unless g - return new createjs.Shape(g) if not points - - g.setStrokeStyle(tailWidth) - g.beginStroke(createjs.Graphics.getRGB(tailColor...)) - g.moveTo(points[0], points[1]) - - headStart = points.length - headLength - [lastX, lastY] = [points[0], points[1]] - - for x, i in points by 2 - continue if i is 0 - y = points[i + 1] - if i >= headStart and not (oneColor and oneWidth) - diff = (i - headStart) / headLength - style = transition(tailWidth, headWidth, diff) - color = colorTransition(tailColor, headColor, diff) - g.setStrokeStyle(style) - g.beginStroke(createjs.Graphics.getRGB(color...)) - g.moveTo(lastX, lastY) if lastX? - - else if dotted - - if false and i < 2 - # Test: footprints - g.beginFill(createjs.Graphics.getRGB(tailColor...)) - xofs = x - lastX - yofs = y - lastY - theta = Math.atan2(yofs, xofs) - [fdist, fwidth] = [4, 2] - fside = if (i + dotOffset) % 4 is 0 then -1 else 1 - fx = [lastX + fside * fdist * (Math.cos(theta) * xofs - Math.sin(theta) * yofs)] - fy = [lastY + fside * fdist * (Math.sin(theta) * xofs - Math.cos(theta) * yofs)] - g.drawCircle(fx, fy, 2) - - offset = ((i / 2) % PATH_SEGMENT_LENGTH) - if offset >= PATH_DOT_LENGTH - if offset is PATH_DOT_LENGTH - g.endStroke() - lastX = x - lastY = y - continue - - else - if offset is 0 - g.beginStroke(createjs.Graphics.getRGB(tailColor...)) - g.moveTo(lastX, lastY) if lastX? - - g.lineTo(x, y) - lastX = x - lastY = y - - g.endStroke() - - s = new createjs.Shape(g) - return s - -colorTransition = (color1, color2, pct) -> - return color1 if pct <= 0 - return color2 if pct >= 1 - - i = 0 - color = [] - while i < 4 - val = transition(color1[i], color2[i], pct) - val = Math.floor(val) if i isnt 3 - color.push(val) - i += 1 - color - -transition = (num1, num2, pct) -> - return num1 if pct <= 0 - return num2 if pct >= 1 - num1 + (num2 - num1) * pct - -colorForThang = (team, brightness=100, alpha=1.0) => - # multipliers should sum to 3.0 - multipliers = [2.0, 0.5, 0.5] if team is 'humans' - multipliers = [0.5, 0.5, 2.0] if team is 'ogres' - multipliers = [2.0, 0.5, 0.5] if not multipliers - color = _.map(multipliers, (m) -> return parseInt(m * brightness)) - color.push(alpha) - return color - -module.exports.createPath = createPath diff --git a/app/lib/world/world.coffee b/app/lib/world/world.coffee index 3b932200f..d640d705d 100644 --- a/app/lib/world/world.coffee +++ b/app/lib/world/world.coffee @@ -472,9 +472,13 @@ module.exports = class World perf.t1 = now() if w.thangs.length - for thangConfig in o.thangs when not w.thangMap[thangConfig.id] - w.thangs.push thang = Thang.deserialize(thangConfig, w, classMap, level.levelComponents) - w.setThang thang + for thangConfig in o.thangs + if thang = w.thangMap[thangConfig.id] + for prop, val of thangConfig.finalState + thang[prop] = val + else + w.thangs.push thang = Thang.deserialize(thangConfig, w, classMap, level.levelComponents) + w.setThang thang else w.thangs = (Thang.deserialize(thang, w, classMap, level.levelComponents) for thang in o.thangs) w.setThang thang for thang in w.thangs @@ -551,7 +555,7 @@ module.exports = class World freeMemoryAfterEachSerialization: -> @frames[i] = null for frame, i in @frames when i < @frames.length - 1 - pointsForThang: (thangID, frameStart=0, frameEnd=null, camera=null, resolution=4) -> + pointsForThang: (thangID, camera=null) -> # Optimized @pointsForThangCache ?= {} cacheKey = thangID @@ -570,16 +574,7 @@ module.exports = class World allPoints.reverse() @pointsForThangCache[cacheKey] = allPoints - points = [] - [lastX, lastY] = [null, null] - for frameIndex in [Math.floor(frameStart / resolution) ... Math.ceil(frameEnd / resolution)] - x = allPoints[frameIndex * 2 * resolution] - y = allPoints[frameIndex * 2 * resolution + 1] - continue if x is lastX and y is lastY - lastX = x - lastY = y - points.push x, y - points + return allPoints actionsForThang: (thangID, keepIdle=false) -> # Optimized diff --git a/app/locale/zh-HANS.coffee b/app/locale/zh-HANS.coffee index 7b578f4f7..75ba7dd1d 100644 --- a/app/locale/zh-HANS.coffee +++ b/app/locale/zh-HANS.coffee @@ -49,13 +49,13 @@ module.exports = nativeDescription: "简体中文", englishDescription: "Chinese play_as: "扮演" # Ladder page spectate: "旁观他人的游戏" # Ladder page players: "玩家" # Hover over a level on /play - hours_played: "已经玩过的时间" # Hover over a level on /play + hours_played: "游戏时长" # Hover over a level on /play items: "道具" # Tooltip on item shop button from /play unlock: "解锁" # For purchasing items and heroes confirm: "确认" owned: "已拥有" # For items you own locked: "需解锁" - available: "可用" + available: "可用" # Available skills_granted: "获得技能" # Property documentation details heroes: "英雄" # Tooltip on hero shop button from /play achievements: "成就" # Tooltip on achievement list button from /play @@ -64,14 +64,14 @@ module.exports = nativeDescription: "简体中文", englishDescription: "Chinese next: "下一步" # Go from choose hero to choose inventory before playing a level change_hero: "重新选择英雄" # Go back from choose inventory to choose hero choose_inventory: "装备道具" - buy_gems: "购买宝石" + buy_gems: "购买宝石" # Buy Gems older_campaigns: "旧的战役" anonymous: "匿名玩家" level_difficulty: "难度:" campaign_beginner: "新手作战" awaiting_levels_adventurer_prefix: "我们每周开放五个关卡" -# awaiting_levels_adventurer: "Sign up as an Adventurer" - awaiting_levels_adventurer_suffix: "做第一个玩新关卡的人" + awaiting_levels_adventurer: "注册成为冒险家" #"Sign up as an Adventurer" + awaiting_levels_adventurer_suffix: "来优先尝试新关卡" #to be the first to play new levels." choose_your_level: "选择关卡" # The rest of this section is the old play view at /play-old and isn't very important. adventurer_prefix: "你可以选择以下任意关卡,或者讨论以上的关卡。到" adventurer_forum: "冒险者论坛" @@ -86,8 +86,8 @@ module.exports = nativeDescription: "简体中文", englishDescription: "Chinese campaign_player_created_description: "……在这里你可以与你的小伙伴的创造力战斗 技术指导." campaign_classic_algorithms: "经典算法" campaign_classic_algorithms_description: "... 你可以在此学习到计算机科学中最常用的算法" -# campaign_forest: "Forest Campaign" -# campaign_dungeon: "Dungeon Campaign" + campaign_forest: "森林战役" #Forest Campaign" + campaign_dungeon: "地牢战役" #Dungeon Campaign" login: sign_up: "注册" @@ -95,10 +95,10 @@ module.exports = nativeDescription: "简体中文", englishDescription: "Chinese logging_in: "正在登录" log_out: "登出" recover: "找回账户" - authenticate_gplus: "通过G+授权" - load_profile: "载入G+资料" - load_email: "载入G+ Email" - finishing: "完成" + authenticate_gplus: "使用 G+ 授权"#Authenticate G+" + load_profile: "载入 G+ 档案" # Load G+ Profile" + load_email: "载入 G+ 电子邮件" #Load G+ Email" + finishing: "完成" #Finishing" signup: create_account_title: "创建一个账户来保存进度" @@ -118,8 +118,8 @@ module.exports = nativeDescription: "简体中文", englishDescription: "Chinese recovery_sent: "找回账户邮件已发送." items: - primary: "主要的" - secondary: "次要的" + primary: "右手"#"Primary" + secondary: "左手"#Secondary" armor: "盔甲" accessories: "配饰" misc: "辅助道具" @@ -172,7 +172,7 @@ module.exports = nativeDescription: "简体中文", englishDescription: "Chinese medium: "中等" hard: "困难" player: "玩家" -# player_level: "Level" # Like player level 5, not like level: Dungeons of Kithgard + player_level: "等级" # Like player level 5, not like level: Dungeons of Kithgard units: second: "秒" @@ -193,7 +193,7 @@ module.exports = nativeDescription: "简体中文", englishDescription: "Chinese play_level: done: "完成" home: "主页" # Not used any more, will be removed soon. -# level: "Level" # Like "Level: Dungeons of Kithgard" + level: "等级" # Like "Level: Dungeons of Kithgard" skip: "跳过" game_menu: "游戏菜单" guide: "指南" diff --git a/app/models/CocoModel.coffee b/app/models/CocoModel.coffee index 4aeff828f..c6238d86b 100644 --- a/app/models/CocoModel.coffee +++ b/app/models/CocoModel.coffee @@ -50,6 +50,8 @@ class CocoModel extends Backbone.Model @loading = false @jqxhr = null @loadFromBackup() + + getCreationDate: -> new Date(parseInt(@id.slice(0,8), 16)*1000) getNormalizedURL: -> "#{@urlRoot}/#{@id}" diff --git a/app/models/ThangType.coffee b/app/models/ThangType.coffee index 07cc58f7a..fa11b19d6 100644 --- a/app/models/ThangType.coffee +++ b/app/models/ThangType.coffee @@ -250,11 +250,13 @@ module.exports = class ThangType extends CocoModel stage.update() stage.startTalking = -> sprite.gotoAndPlay 'portrait' + return # TODO: causes infinite recursion in new EaselJS return if @tick @tick = (e) => @update(e) createjs.Ticker.addEventListener 'tick', @tick stage.stopTalking = -> sprite.gotoAndStop 'portrait' + return # TODO: just breaks in new EaselJS @update() createjs.Ticker.removeEventListener 'tick', @tick @tick = null diff --git a/app/schemas/subscriptions/god.coffee b/app/schemas/subscriptions/god.coffee index 014594893..d7701999a 100644 --- a/app/schemas/subscriptions/god.coffee +++ b/app/schemas/subscriptions/god.coffee @@ -24,6 +24,7 @@ worldUpdatedEventSchema = c.object {required: ['world', 'firstWorld', 'goalState goalStates: goalStatesSchema team: {type: 'string'} firstChangedFrame: {type: 'integer', minimum: 0} + finished: {type: 'boolean'} module.exports = 'god:user-code-problem': c.object {required: ['problem']}, diff --git a/app/schemas/subscriptions/tome.coffee b/app/schemas/subscriptions/tome.coffee index b4530a9a8..2d5d4a966 100644 --- a/app/schemas/subscriptions/tome.coffee +++ b/app/schemas/subscriptions/tome.coffee @@ -35,7 +35,7 @@ module.exports = 'tome:toggle-spell-list': c.object {title: 'Toggle Spell List', description: 'Published when you toggle the dropdown for a thang\'s spells'} - 'tome:reload-code': c.object {title: 'Reload Code', description: 'Published when you reset a spell to its original source', required: ['spell']}, + 'tome:reload-code': c.object {title: 'Reload Code', description: 'Published when you reset a spell to its original source', required: []}, spell: {type: 'object'} 'tome:palette-cleared': c.object {title: 'Palette Cleared', description: 'Published when the spell palette is about to be cleared and recreated.'}, @@ -122,6 +122,10 @@ module.exports = 'tome:required-code-fragment-deleted': c.object {title: 'Required Code Fragment Deleted', description: 'Published when a required code fragment is deleted from the sample code.', required: ['codeFragment']}, codeFragment: {type: 'string'} + 'tome:suspect-code-fragment-added': c.object {title: 'Suspect Code Fragment Added', description: 'Published when a suspect code fragment is added to the sample code.', required: ['codeFragment']}, + codeFragment: {type: 'string'} + codeLanguage: {type: 'string'} + 'tome:winnability-updated': c.object {title: 'Winnability Updated', description: 'When we think we can now win (or can no longer win), we may want to emphasize the submit button versus the run button (or vice versa), so this fires when we get new goal states (even preloaded goal states) suggesting success or failure change.', required: ['winnable']}, winnable: {type: 'boolean'} diff --git a/app/schemas/subscriptions/world.coffee b/app/schemas/subscriptions/world.coffee index bd0b8c2ce..a3da038c3 100644 --- a/app/schemas/subscriptions/world.coffee +++ b/app/schemas/subscriptions/world.coffee @@ -9,6 +9,7 @@ module.exports = thang: {type: 'object'} killer: {type: 'object'} killerHealth: {type: ['number', 'undefined']} + maxHealth: {type: 'number'} 'world:thang-touched-goal': c.object {required: ['actor', 'touched']}, replacedNoteChain: {type: 'array'} diff --git a/app/styles/game-menu/game-menu-modal.sass b/app/styles/game-menu/game-menu-modal.sass index 77dd95eed..8d9c79203 100644 --- a/app/styles/game-menu/game-menu-modal.sass +++ b/app/styles/game-menu/game-menu-modal.sass @@ -20,7 +20,27 @@ top: -146px left: -3px + + //- Close modal button + #close-modal + position: absolute + left: 769px + top: -5px + width: 60px + height: 60px + color: white + text-align: center + font-size: 30px + padding-top: 17px + cursor: pointer + z-index: 2 + @include rotate(-3deg) + + &:hover + color: yellow + + //- Nav bar #game-menu-nav diff --git a/app/styles/game-menu/inventory-modal.sass b/app/styles/game-menu/inventory-modal.sass index f979c2886..e5220b91a 100644 --- a/app/styles/game-menu/inventory-modal.sass +++ b/app/styles/game-menu/inventory-modal.sass @@ -106,11 +106,11 @@ $itemSlotGridHeight: 70px // opacity: 0.5 &.selected - .placeholder, img.item + .placeholder, .item border-color: rgb(81,153,236) background-color: rgb(81,153,236) @include box-shadow(0 0 10px rgb(81,153,236)) - img.item + .item background: rgb(81,153,236) &.should-equip @@ -269,7 +269,7 @@ $itemSlotGridHeight: 70px .placeholder background-position: (-2 * $itemSlotInnerWidth) 0px - img.item + .item position: absolute left: -2px top: -2px @@ -299,7 +299,7 @@ $itemSlotGridHeight: 70px //- Available equipment - &.Warrior #unequipped img.item:not(.Warrior), &.Ranger #unequipped img.item:not(.Ranger), &.Wizard #unequipped img.item:not(.Wizard) + &.Warrior #unequipped .item:not(.Warrior), &.Ranger #unequipped .item:not(.Ranger), &.Wizard #unequipped .item:not(.Wizard) // Our code hides and shows (modifies display), but we can be invisible this other way. visibility: hidden position: absolute @@ -313,7 +313,14 @@ $itemSlotGridHeight: 70px padding: 9px 0 9px 9px #double-click-hint - margin: 20px 50px 70px 0 + margin: 20px 40px 60px 0 + border: 3px solid #8585f4 + padding: 5px + font-weight: bold + .glyphicon + margin-right: 5px + position: relative + top: 2px h4 clear: both @@ -323,14 +330,28 @@ $itemSlotGridHeight: 70px text-transform: uppercase font-weight: bold - img.item + .item float: left border: 1px solid black margin: 3px padding: 1px + position: relative width: 60px - height: 60px - cursor: pointer + + img + width: 56px + height: 56px + display: block + + button + width: 100% + height: 17px + border: 1px solid rgb(46,46,46) + background: white + font-size: 11px + border-radius: 1px + padding: 0 + &.active background-color: rgb(81,153,236) @@ -417,6 +438,9 @@ $itemSlotGridHeight: 70px #selected-item-unlock-button left: 646px + .unequippable + display: none + //- Equip/unequip/extra @@ -437,3 +461,7 @@ $itemSlotGridHeight: 70px border: 3px solid rgb(46,46,46) background: white font-size: 16px + + #equip-item-viewed + background: rgb(84,128,44) + color: white diff --git a/app/styles/play/level.sass b/app/styles/play/level.sass index cad311dad..c5145b627 100644 --- a/app/styles/play/level.sass +++ b/app/styles/play/level.sass @@ -43,6 +43,13 @@ $level-resize-transition-time: 0.5s visibility: hidden #gold-view right: 1% + @include box-shadow(-1px 1px 10px cyan) + .team-gold + font-size: 2vw + line-height: 2vw + img + width: 1.8vw + heighT: 1.8vw #control-bar-view .title left: 20% width: 60% diff --git a/app/styles/play/level/goals.sass b/app/styles/play/level/goals.sass index 310a1ee12..1d8e4a06f 100644 --- a/app/styles/play/level/goals.sass +++ b/app/styles/play/level/goals.sass @@ -12,11 +12,13 @@ padding: 19px 0px 2px 25px z-index: 3 font-size: 14px + min-width: 230px &.brighter font-size: 18px font-size: 1.4vw border-width: 0.91vw 1.22vw 3.10vw 0.91vw + min-width: 23vw .goals-status margin: 5px 0 0 0 diff --git a/app/styles/play/level/gold.sass b/app/styles/play/level/gold.sass index 370d9ec21..bc2331294 100644 --- a/app/styles/play/level/gold.sass +++ b/app/styles/play/level/gold.sass @@ -9,7 +9,7 @@ z-index: 6 @include transition(box-shadow .2s linear) @include user-select(none) - padding: 4px + padding: 0.4vw background: transparent url(/images/level/gold_background.png) no-repeat background-size: 100% 100% border-radius: 4px @@ -18,9 +18,9 @@ box-shadow: 2px 2px 2px black .team-gold - font-size: 18px + font-size: 1.4vw + line-height: 1.4vw margin: 0 - line-height: 20px color: hsla(205,0%,51%,1) display: inline-block padding: 0px 4px @@ -35,11 +35,12 @@ color: hsla(116,80%,51%,1) img - width: 16px - height: 16px + width: 1.2vw + height: 1.2vw border-radius: 2px - padding: 1px - margin-top: -1px + padding: 0.1vw + margin-top: -0.2vw + margin-right: 0.1vw .gold-amount display: inline-block diff --git a/app/styles/play/level/level-dialogue-view.sass b/app/styles/play/level/level-dialogue-view.sass index 298245a73..f29d65f8a 100644 --- a/app/styles/play/level/level-dialogue-view.sass +++ b/app/styles/play/level/level-dialogue-view.sass @@ -55,7 +55,7 @@ line-height: 20px strong - color: #09B057 + color: #FFCCAA .hud-hint font-weight: normal diff --git a/app/styles/play/level/tome/spell.sass b/app/styles/play/level/tome/spell.sass index ed6a9c1a8..b3b0a82ec 100644 --- a/app/styles/play/level/tome/spell.sass +++ b/app/styles/play/level/tome/spell.sass @@ -99,7 +99,7 @@ @include gradient-striped() outline: 2px outset #0099ff @include box-shadow(1px 1px 4px black) - &.playback-ended .executing + &.playback-ended .executing, &.user-code-problem .executing background-color: rgba(50, 255, 80, 0.65) outline: 0 @include box-shadow(0 0 0px black) @@ -125,34 +125,29 @@ // TODO: Pulses too quickly during playback @include animation(pulseRedBackground 1s infinite) - &:not(.playback-ended) + &:not(.playback-ended):not(.user-code-problem) .executing:not(.ace_gutter-cell) background-size: 40px 40px @include animation(progress-bar-stripes 0.5s linear infinite) - .ace_gutter-cell.executing:not(.ace_error):not(.ace_warning):not(.ace_info):after - - // Experimenting with a larger executing-line-pointer - content: "\e072" - position: relative - top: -31px - left: -39px - display: inline-block - font-family: 'Glyphicons Halflings' - font-style: normal - font-weight: normal - line-height: 1 - color: white - text-shadow: 0 0 5px black, 0 0 5px black, 0 0 5px black - font-size: 39px - -webkit-font-smoothing: antialiased - -moz-osx-font-smoothing: grayscale - - - //display: block - //margin-left: 1px - //background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAYAAACNiR0NAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAA2hpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMC1jMDYxIDY0LjE0MDk0OSwgMjAxMC8xMi8wNy0xMDo1NzowMSAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wTU09Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9tbS8iIHhtbG5zOnN0UmVmPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvc1R5cGUvUmVzb3VyY2VSZWYjIiB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iIHhtcE1NOk9yaWdpbmFsRG9jdW1lbnRJRD0ieG1wLmRpZDowMjgwMTE3NDA3MjA2ODExOEE2REU4Q0M1MTM1MkIxRiIgeG1wTU06RG9jdW1lbnRJRD0ieG1wLmRpZDpBQjVEQUNDMzQ4RUIxMUUxOEVGRUUyNzFENDM3RDVFMCIgeG1wTU06SW5zdGFuY2VJRD0ieG1wLmlpZDpBQjVEQUNDMjQ4RUIxMUUxOEVGRUUyNzFENDM3RDVFMCIgeG1wOkNyZWF0b3JUb29sPSJBZG9iZSBQaG90b3Nob3AgQ1M1LjEgTWFjaW50b3NoIj4gPHhtcE1NOkRlcml2ZWRGcm9tIHN0UmVmOmluc3RhbmNlSUQ9InhtcC5paWQ6QTU1MjE3RDIzMTIwNjgxMThEQkI4NTlBMjQ1QTEwOTUiIHN0UmVmOmRvY3VtZW50SUQ9InhtcC5kaWQ6MDI4MDExNzQwNzIwNjgxMThBNkRFOENDNTEzNTJCMUYiLz4gPC9yZGY6RGVzY3JpcHRpb24+IDwvcmRmOlJERj4gPC94OnhtcG1ldGE+IDw/eHBhY2tldCBlbmQ9InIiPz7SazaGAAAAiElEQVR42mL8//8/AzUBEwOVweA3kAWboI2jCyhgDwBx4ZH9ey5Qy4UOQHweaHg/EAtQ08sFUIMDqBmGCkC8HmgoCCtQM1ICoK5toGYsg8KzHmjo+UGbDj8AcSMwORkSnQ7xgA3QtPmApISNBTyAGrSBGl6eAMSGxBhGyIVkZT3G0fKQYgAQYACL+C2ZM6PC7AAAAABJRU5ErkJggg==) - //background-position: 0px center + &:not(.user-code-problem) + .ace_gutter-cell.executing:not(.ace_error):not(.ace_warning):not(.ace_info):after + + // Experimenting with a larger executing-line-pointer + content: "\e072" + position: relative + top: -31px + left: -39px + display: inline-block + font-family: 'Glyphicons Halflings' + font-style: normal + font-weight: normal + line-height: 1 + color: white + text-shadow: 0 0 5px black, 0 0 5px black, 0 0 5px black + font-size: 39px + -webkit-font-smoothing: antialiased + -moz-osx-font-smoothing: grayscale .ace_gutter-cell.executed:not(.ace_error):not(.ace_warning):not(.ace_info) margin-left: 1px diff --git a/app/styles/play/level/tome/spell_palette.sass b/app/styles/play/level/tome/spell_palette.sass index 5b644a3b3..64755f641 100644 --- a/app/styles/play/level/tome/spell_palette.sass +++ b/app/styles/play/level/tome/spell_palette.sass @@ -71,7 +71,7 @@ .property-entry-item-group display: inline-block min-height: 38px - max-width: 212px + width: 212px @include flexbox() @include flex-wrap() @include flex-center() @@ -93,3 +93,8 @@ width: 174px width: -webkit-calc(100% - 38px) width: calc(100% - 38px) + +@media only screen and (max-width: 1100px) + #spell-palette-view + // Make sure we have enough room for at least two columns + padding-left: 12px diff --git a/app/styles/play/level/tome/spell_palette_entry.sass b/app/styles/play/level/tome/spell_palette_entry.sass index cb37378b5..93640e9ef 100644 --- a/app/styles/play/level/tome/spell_palette_entry.sass +++ b/app/styles/play/level/tome/spell_palette_entry.sass @@ -24,6 +24,10 @@ &.pinned background-color: darken(#FFFFFF, 25%) + &.single-entry + height: 38px + line-height: 38px + // Originally pulled these colors from the most relevant textmate-theme classes, but then fudged them a lot. //&.function // color: black @@ -80,6 +84,7 @@ h1:not(.not-code), h2:not(.not-code), h3:not(.not-code), h4:not(.not-code), h5:not(.not-code), h6:not(.not-code) font-family: Menlo, Monaco, Consolas, "Courier New", monospace + font-variant: normal .popover-title background-color: transparent diff --git a/app/styles/play/modal/item-details-view.sass b/app/styles/play/modal/item-details-view.sass index c9e262f5a..b7f9f8e10 100644 --- a/app/styles/play/modal/item-details-view.sass +++ b/app/styles/play/modal/item-details-view.sass @@ -21,8 +21,12 @@ position: absolute left: 860px top: 126px - width: 330px + width: 353px height: 449px + + .nano-content + right: 24px + //background: rgba(100,100,100,0.5) #item-container diff --git a/app/styles/play/modal/play-achievements-modal.sass b/app/styles/play/modal/play-achievements-modal.sass index 5f3652962..db553646e 100644 --- a/app/styles/play/modal/play-achievements-modal.sass +++ b/app/styles/play/modal/play-achievements-modal.sass @@ -1,4 +1,82 @@ #play-achievements-modal - .achievement-view - color: black + + .modal-header + padding-bottom: 20px + img.icon + float: left + width: 40px + margin-right: 10px + + + //- Unachieved Panels + + .panel + margin: 5px 0 + position: relative + border: 2px solid rgb(75,75,75) + padding: 2px + + h3 + margin: 0 0 0 50px + color: rgb(75,75,75) + + p + margin: 0 0 0 50px + color: rgb(75,75,75) + + .panel-body + padding: 5px 150px 5px 5px + border: 2px solid rgb(150,150,150) + + .created + position: absolute + right: 10px + top: 5px + color: rgb(75,75,75) + font-size: 12px + + + //- Achieved Panels + + .panel.earned + background: rgb(50,40,33) + border: 5px solid rgb(26,21,17) + padding: 0 + + h3 + color: white + + p + color: rgb(203,170,148) + + .panel-body + border: 2px solid rgb(75,62,51) + + .created + color: rgb(255,189,68) + + + //- Rewards + + .rewards + position: absolute + right: .2em + //bottom: 10px + top: 29px + + .label + font-size: 18px + margin-left: 5px + color: rgb(50,40,33) + background: rgb(203,170,148) + + .gems + background: #94ccc7 + + .worth + background: #d8c488 + + img + width: 12px + height: 12px \ No newline at end of file diff --git a/app/styles/play/modal/play-items-modal.sass b/app/styles/play/modal/play-items-modal.sass index a8a55e1c4..eddc2b276 100644 --- a/app/styles/play/modal/play-items-modal.sass +++ b/app/styles/play/modal/play-items-modal.sass @@ -339,11 +339,13 @@ .nano-content padding-left: 20px - #item-title - left: 698px - - #item-details-body - left: 648px - - #selected-item-unlock-button - left: 645px + #item-details-view + + #item-title + left: 698px + + #item-details-body + left: 648px + + #selected-item-unlock-button, .unequippable + left: 645px diff --git a/app/templates/game-menu/game-menu-modal.jade b/app/templates/game-menu/game-menu-modal.jade index 3fbfa3d9e..f8fef45e0 100644 --- a/app/templates/game-menu/game-menu-modal.jade +++ b/app/templates/game-menu/game-menu-modal.jade @@ -1,6 +1,9 @@ .modal-dialog .modal-content - img(src="/images/pages/play/modal/game-menu-background.png")#game-menu-background + img(src="/images/pages/play/modal/game-menu-background.png", draggable="false")#game-menu-background + + div#close-modal + span.glyphicon.glyphicon-remove ul#game-menu-nav.nav.nav-pills.nav-stacked li diff --git a/app/templates/game-menu/inventory-modal.jade b/app/templates/game-menu/inventory-modal.jade index 0503d2664..66a6e7397 100644 --- a/app/templates/game-menu/inventory-modal.jade +++ b/app/templates/game-menu/inventory-modal.jade @@ -1,6 +1,6 @@ .modal-dialog .modal-content - img(src="/images/pages/play/modal/inventory-background.png")#play-items-modal-narrow-bg + img(src="/images/pages/play/modal/inventory-background.png", draggable="false")#play-items-modal-narrow-bg div#gems-count-container span#gems-count.big-font= gems @@ -25,21 +25,27 @@ if itemGroups.availableItems.models.length h4#available-description(data-i18n="inventory.available_item") for item in itemGroups.availableItems.models - img.item(src=item.getPortraitURL(), class=item.classes, data-item-id=item.id) + .item.available(class=item.classes, data-item-id=item.id) + img(src=item.getPortraitURL()) + button.btn.equip-item(data-i18n="inventory.equip") .clearfix - #double-click-hint.alert.alert-info.secret(data-i18n="inventory.should_equip") + #double-click-hint.alert.alert-info + span.glyphicon.glyphicon-info-sign.spr + span(data-i18n="inventory.should_equip") if itemGroups.restrictedItems.models.length h4#restricted-description(data-i18n="inventory.restricted_title") for item in itemGroups.restrictedItems.models - img.item(src=item.getPortraitURL(), class=item.classes, data-item-id=item.id) + .item(class=item.classes, data-item-id=item.id) + img(src=item.getPortraitURL(), draggable="false") .clearfix - + if itemGroups.lockedItems.models.length h4#locked-description(data-i18n="play.locked") for item in itemGroups.lockedItems.models - img.item(src=item.getPortraitURL(), class=item.classes, data-item-id=item.id) + .item(class=item.classes, data-item-id=item.id) + img(src=item.getPortraitURL(), draggable="false") .clearfix #item-details-view diff --git a/app/templates/game-menu/options-view.jade b/app/templates/game-menu/options-view.jade index 1fb39cfe2..5f43a07a8 100644 --- a/app/templates/game-menu/options-view.jade +++ b/app/templates/game-menu/options-view.jade @@ -1,7 +1,7 @@ #player-avatar-container(title="Click to change your avatar") if !me.get('photoURL') .editable-icon.glyphicon.glyphicon-pencil - img.profile-photo(src=me.getPhotoURL(230)) + img.profile-photo(src=me.getPhotoURL(230), draggable="false") .form-group input#player-name.profile-caption(name="playerName", type="text", value=me.get('name', true)) @@ -32,7 +32,7 @@ span.help-block(data-i18n="options.autorun_description") Control automatic code execution. - img.hr(src="/images/pages/play/modal/hr.png") + img.hr(src="/images/pages/play/modal/hr.png", draggable="false") h3(data-i18n="options.editor_config_title") Editor Configuration diff --git a/app/templates/play/level/thang_avatar.jade b/app/templates/play/level/thang_avatar.jade index e92cbb7fd..b8e4dd12b 100644 --- a/app/templates/play/level/thang_avatar.jade +++ b/app/templates/play/level/thang_avatar.jade @@ -1,8 +1,8 @@ .thang-avatar-wrapper(class="team-" + (thang.team || "neutral")) //canvas(width=100, height=100, title=thang.id + " - " + thang.team) //- var title = thang.id + " - " + thang.team + (thang.type ? ' - type: "' + thang.type + '"' : '') - img.avatar(src=avatarURL) - img.avatar-frame(src="/images/level/thang_avatar_frame.png") + img.avatar(src=avatarURL, draggable="false") + img.avatar-frame(src="/images/level/thang_avatar_frame.png", draggable="false") .badge.problems .badge.shared-thangs if includeName diff --git a/app/templates/play/level/tome/problem_alert.jade b/app/templates/play/level/tome/problem_alert.jade index cc80b0a53..6951145b8 100644 --- a/app/templates/play/level/tome/problem_alert.jade +++ b/app/templates/play/level/tome/problem_alert.jade @@ -3,7 +3,6 @@ h3.problem-alert-title(data-i18n="play_level.problem_alert_title") Fix Your Code if hint span.problem-title!= hint br - br span.problem-subtitle!= message else span.problem-title!= message diff --git a/app/templates/play/level/tome/spell_palette.jade b/app/templates/play/level/tome/spell_palette.jade index 304ef1a64..f601b8178 100644 --- a/app/templates/play/level/tome/spell_palette.jade +++ b/app/templates/play/level/tome/spell_palette.jade @@ -1,4 +1,4 @@ -img(src="/images/level/code_palette_wood_background.png").code-palette-background +img(src="/images/level/code_palette_wood_background.png", draggable="false").code-palette-background span.code-palette-background if entryGroupSlugs // Non-hero; group by entry groups, or maybe nothing. @@ -15,3 +15,4 @@ else // Hero; group by items, no tabs. h4(data-i18n="play_level.tome_your_skills") .properties + diff --git a/app/templates/play/level/tome/spell_palette_entry_popover.jade b/app/templates/play/level/tome/spell_palette_entry_popover.jade index c13df8394..bd10fc3dd 100644 --- a/app/templates/play/level/tome/spell_palette_entry_popover.jade +++ b/app/templates/play/level/tome/spell_palette_entry_popover.jade @@ -1,5 +1,5 @@ h4 - span= doc.shortName + span.prop-name= doc.shortName | - code.prop-type= doc.type == 'function' && doc.owner == 'this' ? 'method' : doc.type if doc.type != 'function' diff --git a/app/templates/play/modal/item-details-view.jade b/app/templates/play/modal/item-details-view.jade index 6ab305391..a381fa3dd 100644 --- a/app/templates/play/modal/item-details-view.jade +++ b/app/templates/play/modal/item-details-view.jade @@ -7,16 +7,16 @@ .nano .nano-content #item-container - img.item-img(src=item.getPortraitURL()) - img.item-shadow(src=item.getPortraitURL()) + img.item-img(src=item.getPortraitURL(), draggable="false") + img.item-shadow(src=item.getPortraitURL(), draggable="false") - img.hr(src="/images/pages/play/modal/hr.png") + img.hr(src="/images/pages/play/modal/hr.png", draggable="false") for stat in stats div.stat-row.big-font div.stat-label= stat.name div.stat= stat.display - img.hr(src="/images/pages/play/modal/hr.png" class=stat.isLast ? "" : "faded") + img.hr(src="/images/pages/play/modal/hr.png", class=stat.isLast ? "" : "faded", draggable="false") if props.length #skills @@ -27,20 +27,18 @@ span.spr : span!= prop.description - if item.comingSoon - .text-center - h3 This item has no stats. - p Only admins will see it for now. + if item.comingSoon + .text-center + h3 This item has no stats. + p Only admins will see it for now. if item && !item.owned - if item.equippable + if item.unequippable + // Temp, while we only have Warriors: prevent them from buying non-Warrior stuff + button.btn.big-font.disabled.unequippable #{item.get('heroClass')} Only + else button#selected-item-unlock-button.btn.big-font.unlock-button(disabled=!item.affordable, data-item-id=item.id) span(data-i18n="play.unlock") Unlock span.spl= '('+item.get('gems') - img(src="/images/common/gem.png") + img(src="/images/common/gem.png", draggable="false") span ) - else - // Temp, while we only have Warriors: prevent them from buying non-Warrior stuff - button.btn.big-font.disabled.unequippable #{item.get('heroClass')} Only - - \ No newline at end of file diff --git a/app/templates/play/modal/play-achievements-modal.jade b/app/templates/play/modal/play-achievements-modal.jade index 13d244310..71e593170 100644 --- a/app/templates/play/modal/play-achievements-modal.jade +++ b/app/templates/play/modal/play-achievements-modal.jade @@ -4,4 +4,29 @@ block modal-header-content h3(data-i18n="play.achievements") Achievements block modal-body-content - p TODO: show all dem achievements + for achievement in achievements + .panel(class=achievement.earned ? 'earned' : '') + .panel-body + img.icon(src=achievement.getImageURL(), draggable="false") + h3= achievement.name + p= achievement.description + + if achievement.earnedDate + .created=moment(achievement.earnedDate).fromNow() + else + .created(data-i18n="user.status_unfinished") + + .rewards + - rewards = achievement.get('rewards') + - rewards = { gems: 100 } + if rewards && rewards.gems + span.gems.label.label-default + span= rewards.gems + img.gem(src="/images/common/gem.png", draggable="false") + + - worth = achievement.get('worth') + if worth + span.worth.label.label-default + span #{worth}xp + // maybe add more icons/numbers for items, heroes, levels, xp? +block modal-footer \ No newline at end of file diff --git a/app/templates/play/modal/play-heroes-modal.jade b/app/templates/play/modal/play-heroes-modal.jade index 7de87994b..92c06b6b6 100644 --- a/app/templates/play/modal/play-heroes-modal.jade +++ b/app/templates/play/modal/play-heroes-modal.jade @@ -1,7 +1,7 @@ .modal-dialog .modal-content - img(src="/images/pages/play/modal/heroes-background.png")#play-heroes-background + img(src="/images/pages/play/modal/heroes-background.png", draggable="false")#play-heroes-background h1(data-i18n="choose_hero.choose_hero") @@ -15,13 +15,13 @@ li(data-hero-id=hero.get('original'), title=hero.name, data-slide-to=index, data-target="#hero-carousel", class="hero-indicator hero-index-" + index + (hero.locked ? " locked" : "")) .hero-avatar if hero.locked - img.lock-indicator(src="/images/pages/game-menu/lock.png") + img.lock-indicator(src="/images/pages/game-menu/lock.png", draggable="false") .carousel-inner for hero in heroes div(class="item hero-item" + (hero.locked ? " locked" : ""), data-hero-id=hero.get('original')) canvas.hero-canvas .hero-feature-image - img + img(draggable="false") .hero-stats h2= hero.name .hero-description= hero.description diff --git a/app/templates/play/modal/play-items-modal.jade b/app/templates/play/modal/play-items-modal.jade index ec6b2d21d..3e81ebf53 100644 --- a/app/templates/play/modal/play-items-modal.jade +++ b/app/templates/play/modal/play-items-modal.jade @@ -1,7 +1,7 @@ .modal-dialog .modal-content - img(src="/images/pages/play/modal/items-background.png")#play-items-modal-bg - img(src="/images/pages/play/modal/items-background-narrow.png")#play-items-modal-narrow-bg + img(src="/images/pages/play/modal/items-background.png", draggable="false")#play-items-modal-bg + img(src="/images/pages/play/modal/items-background-narrow.png", draggable="false")#play-items-modal-narrow-bg h1.big-font(data-i18n="play.items") @@ -15,7 +15,7 @@ for category, index in itemCategories li(class=index ? "" : "active") a.one-line(href="#item-category-" + category, data-toggle="tab") - img.tab-icon(src="/images/pages/play/modal/item-icon-"+category+".png") + img.tab-icon(src="/images/pages/play/modal/item-icon-"+category+".png", draggable="false") span.big-font= itemCategoryNames[index] @@ -31,15 +31,15 @@ if item.silhouetted && !item.owned span.glyphicon.glyphicon-lock.bolder span.glyphicon.glyphicon-lock - img.item-silhouette(src=item.getPortraitURL()) + img.item-silhouette(src=item.getPortraitURL(), draggable="false") if item.level .required-level div(data-i18n="general.player_level") div= item.level else strong.big-font= item.name - img.item-img(src=item.getPortraitURL()) - img.item-shadow(src=item.getPortraitURL()) + img.item-img(src=item.getPortraitURL(), draggable="false") + img.item-shadow(src=item.getPortraitURL(), draggable="false") if item.owned span.big-font.owned(data-i18n="play.owned") @@ -47,7 +47,7 @@ span.big-font.locked(data-i18n="play.locked") else span.cost - img(src="/images/common/gem.png") + img(src="/images/common/gem.png", draggable="false") span.big-font= item.get('gems') if item.equippable // Temp, while we only have Warriors: prevent them from buying non-Warrior stuff diff --git a/app/templates/play/world-map-view.jade b/app/templates/play/world-map-view.jade index b56457b49..f579a1d0e 100644 --- a/app/templates/play/world-map-view.jade +++ b/app/templates/play/world-map-view.jade @@ -3,7 +3,7 @@ .gradient.vertical-gradient.right-gradient .gradient.horizontal-gradient.bottom-gradient .gradient.vertical-gradient.left-gradient - img.map-background(src="/images/pages/play/map_" + mapType + ".jpg", alt="") + img.map-background(src="/images/pages/play/map_" + mapType + ".jpg", alt="", draggable="false") - var seenNext = nextLevel; each level in campaign.levels @@ -43,10 +43,10 @@ .game-controls.header-font button.btn.items(data-toggle='coco-modal', data-target='play/modal/PlayItemsModal', data-i18n="[title]play.items") button.btn.heroes(data-toggle='coco-modal', data-target='play/modal/PlayHeroesModal', data-i18n="[title]play.heroes") + button.btn.achievements(data-toggle='coco-modal', data-target='play/modal/PlayAchievementsModal', data-i18n="[title]play.achievements") if me.get('anonymous') === false button.btn.gems(data-toggle='coco-modal', data-target='play/modal/BuyGemsModal', data-i18n="[title]play.buy_gems") if me.isAdmin() - button.btn.achievements(data-toggle='coco-modal', data-target='play/modal/PlayAchievementsModal', data-i18n="[title]play.achievements") button.btn.account(data-toggle='coco-modal', data-target='play/modal/PlayAccountModal', data-i18n="[title]play.account") button.btn.settings(data-toggle='coco-modal', data-target='play/modal/PlaySettingsModal', data-i18n="[title]play.settings") else if me.get('anonymous', true) diff --git a/app/views/game-menu/GameMenuModal.coffee b/app/views/game-menu/GameMenuModal.coffee index 0e078115f..3e52e1bd8 100644 --- a/app/views/game-menu/GameMenuModal.coffee +++ b/app/views/game-menu/GameMenuModal.coffee @@ -17,6 +17,7 @@ module.exports = class GameMenuModal extends ModalView 'change input.select': 'onSelectionChanged' 'shown.bs.tab #game-menu-nav a': 'onTabShown' 'click #change-hero-tab': -> @trigger 'change-hero' + 'click #close-modal': 'hide' constructor: (options) -> super options diff --git a/app/views/game-menu/InventoryModal.coffee b/app/views/game-menu/InventoryModal.coffee index b5e3491bc..c5baa7e20 100644 --- a/app/views/game-menu/InventoryModal.coffee +++ b/app/views/game-menu/InventoryModal.coffee @@ -20,9 +20,10 @@ module.exports = class InventoryModal extends ModalView events: 'click .item-slot': 'onItemSlotClick' - 'click #unequipped img.item': 'onUnequippedItemClick' - 'doubletap #unequipped img.item': 'onUnequippedItemDoubleClick' - 'doubletap .item-slot img.item': 'onEquippedItemDoubleClick' + 'click #unequipped .item': 'onUnequippedItemClick' + 'doubletap #unequipped .item': 'onUnequippedItemDoubleClick' + 'doubletap .item-slot .item': 'onEquippedItemDoubleClick' + 'click button.equip-item': 'onClickEquipItemButton' 'shown.bs.modal': 'onShown' 'click #choose-hero-button': 'onClickChooseHero' 'click #play-level-button': 'onClickPlayLevel' @@ -83,9 +84,11 @@ module.exports = class InventoryModal extends ModalView # sort into one of the four groups locked = not (item.get('original') in me.items()) - locked = false if me.get('slug') is 'nick' + #locked = false if me.get('slug') is 'nick' - if locked and item.get('slug') isnt 'simple-boots' + if not item.getFrontFacingStats().props.length and not _.size(item.getFrontFacingStats().stats) and not locked # Temp: while there are placeholder items + null # Don't put into a collection + else if locked and item.get('slug') isnt 'simple-boots' @itemGroups.lockedItems.add(item) item.classes.push 'locked' item.classes.push 'silhouette' if item.isSilhouettedItem() @@ -126,6 +129,7 @@ module.exports = class InventoryModal extends ModalView @insertSubView(@itemDetailsView) @requireLevelEquipment() @$el.find('.nano').nanoScroller({alwaysVisible: true}) + @onSelectionChanged() afterInsert: -> super() @@ -137,7 +141,7 @@ module.exports = class InventoryModal extends ModalView #- Draggable logic setUpDraggableEventsForAvailableEquipment: -> - for availableItemEl in @$el.find('#unequipped img.item') + for availableItemEl in @$el.find('#unequipped .item') availableItemEl = $(availableItemEl) continue if availableItemEl.hasClass('locked') or availableItemEl.hasClass('restricted') dragHelper = availableItemEl.clone().addClass('draggable-item') @@ -201,12 +205,12 @@ module.exports = class InventoryModal extends ModalView onUnequippedItemClick: (e) -> return if @justDoubleClicked - itemEl = $(e.target).closest('img.item') + itemEl = $(e.target).closest('.item') @selectUnequippedItem(itemEl) onUnequippedItemDoubleClick: (e) -> - item = $(e.target).closest('img.item') - return if item.hasClass('locked') or item.hasClass('restricted') + itemEl = $(e.target).closest('.item') + return if itemEl.hasClass('locked') or itemEl.hasClass('restricted') @equipSelectedItem() @justDoubleClicked = true _.defer => @justDoubleClicked = false @@ -215,6 +219,11 @@ module.exports = class InventoryModal extends ModalView onClickEquipItemViewed: -> @equipSelectedItem() onClickUnequipItemViewed: -> @unequipSelectedItem() + onClickEquipItemButton: (e) -> + itemEl = $(e.target).closest('.item') + @selectUnequippedItem(itemEl) + @equipSelectedItem() + onUnlockButtonClicked: (e) -> button = $(e.target).closest('button') if button.hasClass('confirm') @@ -249,7 +258,7 @@ module.exports = class InventoryModal extends ModalView selectItemSlot: (slotEl) -> @clearSelection() slotEl.addClass('selected') - selectedSlotItemID = slotEl.find('img.item').data('item-id') + selectedSlotItemID = slotEl.find('.item').data('item-id') item = @items.get(selectedSlotItemID) if item then @showItemDetails(item, 'unequip') @onSelectionChanged() @@ -270,7 +279,7 @@ module.exports = class InventoryModal extends ModalView selectedItemEl.effect('transfer', to: slotEl, duration: 500, easing: 'easeOutCubic') unequipped = @unequipItemFromSlot(slotEl) selectedItemEl.addClass('equipped') - slotEl.append(selectedItemEl.clone()) + slotEl.append(selectedItemEl.find('img').clone().addClass('item').data('item-id', selectedItem.id)) @clearSelection() @showItemDetails(selectedItem, 'unequip') slotEl.addClass('selected') @@ -302,17 +311,17 @@ module.exports = class InventoryModal extends ModalView @hideItemDetails() unequipItemFromSlot: (slotEl) -> - itemEl = slotEl.find('img.item') + itemEl = slotEl.find('.item') itemIDToUnequip = itemEl.data('item-id') return unless itemIDToUnequip itemEl.remove() - @$el.find("#unequipped img.item[data-item-id=#{itemIDToUnequip}]").removeClass('equipped') + @$el.find("#unequipped .item[data-item-id=#{itemIDToUnequip}]").removeClass('equipped') deselectAllSlots: -> @$el.find('#equipped .item-slot.selected').removeClass('selected') deselectAllUnequippedItems: -> - @$el.find('#unequipped img.item').removeClass('active') + @$el.find('#unequipped .item').removeClass('active') getSlot: (name) -> @$el.find(".item-slot[data-slot=#{name}]") @@ -321,9 +330,13 @@ module.exports = class InventoryModal extends ModalView @$el.find('#equipped .item-slot.selected') getSelectedUnequippedItem: -> - @$el.find('#unequipped img.item.active') + @$el.find('#unequipped .item.active') onSelectionChanged: -> + heroClass = @selectedHero?.get('heroClass') + itemsCanBeEquipped = @$el.find('#unequipped .item.available:not(.equipped)').filter('.'+heroClass).length + toShow = @$el.find('#double-click-hint, #available-description') + if itemsCanBeEquipped then toShow.removeClass('secret') else toShow.addClass('secret') @delegateEvents() @@ -340,7 +353,7 @@ module.exports = class InventoryModal extends ModalView config = {} for slot in @$el.find('.item-slot') slotName = $(slot).data('slot') - slotItemID = $(slot).find('img.item').data('item-id') + slotItemID = $(slot).find('.item').data('item-id') continue unless slotItemID item = _.find @items.models, {id:slotItemID} config[slotName] = item.get('original') @@ -387,7 +400,6 @@ module.exports = class InventoryModal extends ModalView @highlightElement availableSlotSelector, delay: 500, sides: ['right'], rotation: Math.PI / 2 @$el.find(availableSlotSelector).addClass 'should-equip' @$el.find("#equipped div[data-slot='#{slot}']").addClass 'should-equip' - @$el.find('#double-click-hint').removeClass('secret') @remainingRequiredEquipment.push slot: slot, item: gear[item] if hadRequired and not @remainingRequiredEquipment.length @endHighlight() diff --git a/app/views/modal/AuthModal.coffee b/app/views/modal/AuthModal.coffee index 5ae45badc..aa4c25cad 100644 --- a/app/views/modal/AuthModal.coffee +++ b/app/views/modal/AuthModal.coffee @@ -126,8 +126,6 @@ module.exports = class AuthModal extends ModalView step.done = false for step in @gplusAuthSteps handler = application.gplusHandler - @renderGPlusAuthChecklist() - @listenToOnce handler, 'logged-in', -> @gplusAuthSteps[0].done = true @renderGPlusAuthChecklist() diff --git a/app/views/play/WorldMapView.coffee b/app/views/play/WorldMapView.coffee index fc556515d..a6e45686c 100644 --- a/app/views/play/WorldMapView.coffee +++ b/app/views/play/WorldMapView.coffee @@ -102,7 +102,7 @@ module.exports = class WorldMapView extends RootView context.isIPadApp = application.isIPadApp context.mapType = _.string.slugify @terrain context.nextLevel = @nextLevel - context.forestIsAvailable = '541b67f71ccc8eaae19f3c62' in (me.get('earned')?.levels or []) + context.forestIsAvailable = @startedForestLevel or '541b67f71ccc8eaae19f3c62' in (me.get('earned')?.levels or []) context afterRender: -> @@ -131,8 +131,10 @@ module.exports = class WorldMapView extends RootView @openModalView authModal onSessionsLoaded: (e) -> + forestLevels = (f.id for f in forest) for session in @sessions.models @levelStatusMap[session.get('levelID')] = if session.get('state')?.complete then 'complete' else 'started' + @startedForestLevel = true if session.get('levelID') in forestLevels if @nextLevel and @levelStatusMap[@nextLevel] is 'complete' @nextLevel = null @render() @@ -771,3 +773,4 @@ if me.getKithmazeGroup() is 'the-first-kithmaze' _.remove dungeon, id: 'haunted-kithmaze' else _.remove dungeon, id: 'the-first-kithmaze' + _.find(dungeon, id: 'the-raised-sword').nextLevels.continue = 'haunted-kithmaze' diff --git a/app/views/play/level/LevelHUDView.coffee b/app/views/play/level/LevelHUDView.coffee index 669da3687..23a6c9617 100644 --- a/app/views/play/level/LevelHUDView.coffee +++ b/app/views/play/level/LevelHUDView.coffee @@ -79,7 +79,7 @@ module.exports = class LevelHUDView extends CocoView wrapper.removeClass (i, css) -> (css.match(/\bteam-\S+/g) or []).join ' ' wrapper.addClass "team-#{team}" if thangType.get('raster') - wrapper.empty().append($('').addClass('avatar').attr('src', '/file/'+thangType.get('raster'))) + wrapper.empty().append($('').addClass('avatar').attr('src', '/file/'+thangType.get('raster'))) else return unless stage = thangType.getPortraitStage options, 100 newCanvas = $(stage.canvas).addClass('thang-canvas avatar') @@ -87,7 +87,7 @@ module.exports = class LevelHUDView extends CocoView stage.update() @stage?.stopTalking() @stage = stage - wrapper.append($('').addClass('avatar-frame').attr('src', '/images/level/thang_avatar_frame.png')) + wrapper.append($('').addClass('avatar-frame').attr('src', '/images/level/thang_avatar_frame.png')) onThangBeganTalking: (e) -> return unless @stage and @thang is e.thang diff --git a/app/views/play/level/LevelPlaybackView.coffee b/app/views/play/level/LevelPlaybackView.coffee index af4ef02f2..5f60d900e 100644 --- a/app/views/play/level/LevelPlaybackView.coffee +++ b/app/views/play/level/LevelPlaybackView.coffee @@ -210,6 +210,7 @@ module.exports = class LevelPlaybackView extends CocoView onProgressHover: (e, offsetX) -> timeRatio = @$progressScrubber.width() / @totalTime offsetX ?= e.clientX - $(e.target).closest('#timeProgress').offset().left + offsetX = Math.max 0, offsetX @newTime = offsetX / timeRatio @updatePopupContent() @timePopup?.onHover e diff --git a/app/views/play/level/modal/ReloadLevelModal.coffee b/app/views/play/level/modal/ReloadLevelModal.coffee index 320ecde21..d11bb4239 100644 --- a/app/views/play/level/modal/ReloadLevelModal.coffee +++ b/app/views/play/level/modal/ReloadLevelModal.coffee @@ -6,4 +6,10 @@ module.exports = class ReloadLevelModal extends ModalView template: template events: - 'click #restart-level-confirm-button': -> Backbone.Mediator.publish 'level:restart', {} + 'click #restart-level-confirm-button': 'onClickRestart' + + onClickRestart: (e) -> + if key.shift + Backbone.Mediator.publish 'level:restart', {} + else + Backbone.Mediator.publish 'tome:reload-code', {} diff --git a/app/views/play/level/tome/ProblemAlertView.coffee b/app/views/play/level/tome/ProblemAlertView.coffee index 907621365..a94987bf7 100644 --- a/app/views/play/level/tome/ProblemAlertView.coffee +++ b/app/views/play/level/tome/ProblemAlertView.coffee @@ -34,8 +34,18 @@ module.exports = class ProblemAlertView extends CocoView getRenderData: (context={}) -> context = super context if @problem? - format = (s) -> s?.replace(//g, '>').replace(/\n/g, '
') - context.message = format @problem.aetherProblem.message + format = (s) -> marked(s.replace(//g, '>')) if s? + message = @problem.aetherProblem.message + # Add time to problem message if hint is for a missing null check + # NOTE: This may need to be updated with Aether error hint changes + if @problem.aetherProblem.hint? and /(?:null|undefined)/.test @problem.aetherProblem.hint + age = @problem.aetherProblem.userInfo?.age + if age? + if /^Line \d+:/.test message + message = message.replace /^(Line \d+)/, "$1, time #{age.toFixed(1)}" + else + message = "Time #{age.toFixed(1)}: #{message}" + context.message = format message context.hint = format @problem.aetherProblem.hint context diff --git a/app/views/play/level/tome/SpellPaletteView.coffee b/app/views/play/level/tome/SpellPaletteView.coffee index 9d3fd3a0a..6a89679f1 100644 --- a/app/views/play/level/tome/SpellPaletteView.coffee +++ b/app/views/play/level/tome/SpellPaletteView.coffee @@ -51,10 +51,20 @@ module.exports = class SpellPaletteView extends CocoView @entryGroupElements = {} for group, entries of @entryGroups @entryGroupElements[group] = itemGroup = $('
').appendTo @$el.find('.properties') - itemGroup.append $('').attr('src', entries[0].options.item.getPortraitURL()).css('top', Math.max(0, 19 * (entries.length - 2) / 2) + 2) if entries[0].options.item?.getPortraitURL - for entry in entries + if entries[0].options.item?.getPortraitURL + itemImage = $('').attr('src', entries[0].options.item.getPortraitURL()).css('top', Math.max(0, 19 * (entries.length - 2) / 2) + 2) + itemGroup.append itemImage + firstEntry = entries[0] + do (firstEntry) -> + itemImage.on "mouseenter", (e) -> firstEntry.onMouseEnter e + itemImage.on "mouseleave", (e) -> firstEntry.onMouseLeave e + for entry, entryIndex in entries itemGroup.append entry.el entry.render() # Render after appending so that we can access parent container for popover + if entries.length is 1 + entry.$el.addClass 'single-entry' + if entryIndex is 0 + entry.$el.addClass 'first-entry' @$el.addClass 'hero' @updateMaxHeight() unless application.isIPadApp diff --git a/app/views/play/level/tome/SpellView.coffee b/app/views/play/level/tome/SpellView.coffee index cb4e05143..c1429a400 100644 --- a/app/views/play/level/tome/SpellView.coffee +++ b/app/views/play/level/tome/SpellView.coffee @@ -189,7 +189,7 @@ module.exports = class SpellView extends CocoView bindKey: {win: 'Ctrl-Shift-M', mac: 'Command-Shift-M|Ctrl-Shift-M'} exec: -> Backbone.Mediator.publish 'tome:toggle-maximize', {} addCommand - # TODO: Restrict to beginner campaign levels + # TODO: Restrict to beginner campaign levels, possibly with a CampaignOptions similar to LevelOptions name: 'enter-skip-delimiters' bindKey: 'Enter|Return' exec: => @@ -202,6 +202,10 @@ module.exports = class SpellView extends CocoView newRange.setEnd newRange.end.row, newRange.end.column + delimMatch[1].length @aceSession.selection.setSelectionRange newRange @ace.execCommand 'insertstring', '\n' + addCommand + name: 'disable-spaces' + bindKey: 'Space' + exec: => @ace.execCommand 'insertstring', ' ' unless LevelOptions[@options.level.get('slug')]?.disableSpaces fillACE: -> @ace.setValue @spell.source @@ -390,7 +394,7 @@ module.exports = class SpellView extends CocoView @focus() if cast onCodeReload: (e) -> - return unless e.spell is @spell + return unless e.spell is @spell or not e.spell @reloadCode true @ace.clearSelection() _.delay (=> @ace?.clearSelection()), 500 # Make double sure this gets done (saw some timing issues?) @@ -446,7 +450,8 @@ module.exports = class SpellView extends CocoView _.throttle @updateLines, 500 _.throttle @hideProblemAlert, 500 ] - onSignificantChange.push _.debounce @checkRequiredCode, 1500 if requiredCodePerLevel[@options.level.get('slug')] + onSignificantChange.push _.debounce @checkRequiredCode, 750 if LevelOptions[@options.level.get('slug')]?.requiredCode + onSignificantChange.push _.debounce @checkSuspectCode, 750 if LevelOptions[@options.level.get('slug')]?.suspectCode @onCodeChangeMetaHandler = => return if @eventsSuppressed Backbone.Mediator.publish 'audio-player:play-sound', trigger: 'code-change', volume: 0.5 @@ -877,13 +882,26 @@ module.exports = class SpellView extends CocoView checkRequiredCode: => return if @destroyed source = @getSource().replace @singleLineCommentRegex(), '' - for requiredCodeFragment in requiredCodePerLevel[@options.level.get('slug')] + requiredCodeFragments = LevelOptions[@options.level.get('slug')].requiredCode + for requiredCodeFragment in requiredCodeFragments + # Could make this obey regular expressions like suspectCode if needed if source.indexOf(requiredCodeFragment) is -1 @warnedCodeFragments ?= {} unless @warnedCodeFragments[requiredCodeFragment] Backbone.Mediator.publish 'tome:required-code-fragment-deleted', codeFragment: requiredCodeFragment @warnedCodeFragments[requiredCodeFragment] = true + checkSuspectCode: => + return if @destroyed + source = @getSource().replace @singleLineCommentRegex(), '' + suspectCodeFragments = LevelOptions[@options.level.get('slug')].suspectCode + for suspectCodeFragment in suspectCodeFragments + if suspectCodeFragment.pattern.test source + @warnedCodeFragments ?= {} + unless @warnedCodeFragments[suspectCodeFragment.name] + Backbone.Mediator.publish 'tome:suspect-code-fragment-added', codeFragment: suspectCodeFragment.name, codeLanguage: @spell.language + @warnedCodeFragments[suspectCodeFragment] = true + destroy: -> $(@ace?.container).find('.ace_gutter').off 'click', '.ace_error, .ace_warning, .ace_info', @onAnnotationClick $(@ace?.container).find('.ace_gutter').off 'click', @onGutterClick @@ -896,11 +914,3 @@ module.exports = class SpellView extends CocoView @debugView?.destroy() $(window).off 'resize', @onWindowResize super() - - -requiredCodePerLevel = - 'dungeons-of-kithgard': ['moveRight'] - 'true-names': ['Brak'] - 'the-first-kithmaze': ['loop'] - 'haunted-kithmaze': ['loop'] - 'lowly-kithmen': ['findNearestEnemy'] diff --git a/app/views/play/modal/ItemDetailsView.coffee b/app/views/play/modal/ItemDetailsView.coffee index 149cd511f..8c860fc3f 100644 --- a/app/views/play/modal/ItemDetailsView.coffee +++ b/app/views/play/modal/ItemDetailsView.coffee @@ -44,7 +44,6 @@ module.exports = class ItemDetailsView extends CocoView @listenToOnce docs, 'sync', @onDocsLoaded @render() - @$el.find('.nano:visible').nanoScroller() onDocsLoaded: (levelComponents) -> for component in levelComponents.models @@ -55,6 +54,10 @@ module.exports = class ItemDetailsView extends CocoView @propDocs[propDoc.name] = propDoc @render() + afterRender: -> + super() + @$el.find('.nano:visible').nanoScroller({alwaysVisible: true}) + getRenderData: -> c = super() c.item = @item diff --git a/app/views/play/modal/PlayAchievementsModal.coffee b/app/views/play/modal/PlayAchievementsModal.coffee index ca0940b88..360330685 100644 --- a/app/views/play/modal/PlayAchievementsModal.coffee +++ b/app/views/play/modal/PlayAchievementsModal.coffee @@ -2,27 +2,92 @@ ModalView = require 'views/kinds/ModalView' template = require 'templates/play/modal/play-achievements-modal' CocoCollection = require 'collections/CocoCollection' Achievement = require 'models/Achievement' -#AchievementView = require 'views/game-menu/AchievementView' +EarnedAchievement = require 'models/EarnedAchievement' + +utils = require 'lib/utils' + +PAGE_SIZE = 200 module.exports = class PlayAchievementsModal extends ModalView className: 'modal fade play-modal' template: template modalWidthPercent: 90 id: 'play-achievements-modal' - #instant: true - - #events: - # 'change input.select': 'onSelectionChanged' + plain: true + + earnedMap: {} constructor: (options) -> super options - #@achievements = new CocoCollection([], {model: Achievement}) - #@achievements.url = '/db/thang.type?view=achievements&project=name,description,components,original,rasterIcon' - #@supermodel.loadCollection(@achievements, 'achievements') + @achievements = new Backbone.Collection() + earnedMap = {} + + achievementsFetcher = new CocoCollection([], {url: '/db/achievement', model: Achievement}) + achievementsFetcher.setProjection([ + 'name' + 'description' + 'icon' + 'worth' + 'i18n' + 'rewards' + 'collection' + ]) + + earnedAchievementsFetcher = new CocoCollection([], {url: '/db/earned_achievement', model: EarnedAchievement}) + earnedAchievementsFetcher.setProjection([ 'achievement' ]) + + achievementsFetcher.skip = 0 + achievementsFetcher.fetch({data: {skip: 0, limit: PAGE_SIZE}}) + earnedAchievementsFetcher.skip = 0 + earnedAchievementsFetcher.fetch({data: {skip: 0, limit: PAGE_SIZE}}) + + @listenTo achievementsFetcher, 'sync', @onAchievementsLoaded + @listenTo earnedAchievementsFetcher, 'sync', @onEarnedAchievementsLoaded + + @supermodel.loadCollection(achievementsFetcher, 'achievement') + @supermodel.loadCollection(earnedAchievementsFetcher, 'achievement') + + @onEverythingLoaded = _.after(2, @onEverythingLoaded) + + onAchievementsLoaded: (fetcher) -> + needMore = fetcher.models.length is PAGE_SIZE + @achievements.add(fetcher.models) + if needMore + fetcher.skip += PAGE_SIZE + fetcher.fetch({data: {skip: fetcher.skip, limit: PAGE_SIZE}}) + else + @stopListening(fetcher) + @onEverythingLoaded() + + onEarnedAchievementsLoaded: (fetcher) -> + needMore = fetcher.models.length is PAGE_SIZE + for earned in fetcher.models + @earnedMap[earned.get('achievement')] = earned + if needMore + fetcher.skip += PAGE_SIZE + fetcher.fetch({data: {skip: fetcher.skip, limit: PAGE_SIZE}}) + else + @stopListening(fetcher) + @onEverythingLoaded() + + onEverythingLoaded: => + @achievements.set(@achievements.filter((m) -> m.get('collection') isnt 'level.sessions')) + for achievement in @achievements.models + if earned = @earnedMap[achievement.id] + achievement.earned = earned + achievement.earnedDate = earned.getCreationDate() + achievement.earnedDate ?= '' + @achievements.comparator = (m) -> m.earnedDate + @achievements.sort() + @achievements.set(@achievements.models.reverse()) + for achievement in @achievements.models + achievement.name = utils.i18n achievement.attributes, 'name' + achievement.description = utils.i18n achievement.attributes, 'description' + @render() getRenderData: (context={}) -> context = super(context) - #context.achievements = @achievements.models + context.achievements = @achievements.models context afterRender: -> diff --git a/app/views/play/modal/PlayItemsModal.coffee b/app/views/play/modal/PlayItemsModal.coffee index 6ccbd123c..d0e77f8d0 100644 --- a/app/views/play/modal/PlayItemsModal.coffee +++ b/app/views/play/modal/PlayItemsModal.coffee @@ -89,7 +89,7 @@ module.exports = class PlayItemsModal extends ModalView model.affordable = cost <= gemsOwned model.silhouetted = not model.owned and model.isSilhouettedItem() model.level = model.levelRequiredForItem() if model.get('tier')? - model.equippable = 'Warrior' in model.getAllowedHeroClasses() # Temp: while there are no wizards/rangers + model.unequippable = not ('Warrior' in model.getAllowedHeroClasses()) # Temp: while there are no wizards/rangers model.comingSoon = not model.getFrontFacingStats().props.length and not _.size model.getFrontFacingStats().stats and not model.owned # Temp: while there are placeholder items @idToItem[model.id] = model diff --git a/bin/coco-update-treema b/bin/coco-update-treema deleted file mode 100755 index 163d3e8e9..000000000 --- a/bin/coco-update-treema +++ /dev/null @@ -1,3 +0,0 @@ -cd ~/Desktop/coco -cp ~/Desktop/treema/treema.js ./vendor/scripts/ -cp ~/Desktop/treema/treema.css ./vendor/styles/ diff --git a/server/achievements/EarnedAchievement.coffee b/server/achievements/EarnedAchievement.coffee index 685a502c6..80d6f6249 100644 --- a/server/achievements/EarnedAchievement.coffee +++ b/server/achievements/EarnedAchievement.coffee @@ -2,12 +2,6 @@ mongoose = require 'mongoose' jsonschema = require '../../app/schemas/models/earned_achievement' EarnedAchievementSchema = new mongoose.Schema({ - created: - type: Date - default: Date.now - changed: - type: Date - default: Date.now notified: type: Boolean default: false diff --git a/server/achievements/earned_achievement_handler.coffee b/server/achievements/earned_achievement_handler.coffee index e39e1dd4c..7eb2c7437 100644 --- a/server/achievements/earned_achievement_handler.coffee +++ b/server/achievements/earned_achievement_handler.coffee @@ -16,7 +16,26 @@ class EarnedAchievementHandler extends Handler get: (req, res) -> return @getByAchievementIDs(req, res) if req.query.view is 'get-by-achievement-ids' - super(arguments...) + query = { user: req.user._id+''} + + projection = {} + if req.query.project + projection[field] = 1 for field in req.query.project.split(',') + + q = EarnedAchievement.find(query, projection) + + skip = parseInt(req.query.skip) + if skip? and skip < 1000000 + q.skip(skip) + + limit = parseInt(req.query.limit) + if limit? and limit < 1000 + q.limit(limit) + + q.exec (err, documents) => + return @sendDatabaseError(res, err) if err + documents = (@formatEntity(req, doc) for doc in documents) + @sendSuccess(res, documents) getByAchievementIDs: (req, res) -> query = { user: req.user._id+''} diff --git a/server/commons/Handler.coffee b/server/commons/Handler.coffee index 15ecc3b11..b9a5d6f2e 100644 --- a/server/commons/Handler.coffee +++ b/server/commons/Handler.coffee @@ -129,6 +129,10 @@ module.exports = class Handler term = req.query.term matchedObjects = [] filters = if @modelClass.schema.uses_coco_versions or @modelClass.schema.uses_coco_permissions then [filter: {index: true}] else [filter: {}] + + skip = parseInt(req.query.skip) + limit = parseInt(req.query.limit) + if @modelClass.schema.uses_coco_permissions and req.user filters.push {filter: {index: req.user.get('id')}} projection = null @@ -158,7 +162,14 @@ module.exports = class Handler else args = [filter.filter] args.push projection if projection - @modelClass.find(args...).limit(FETCH_LIMIT).exec callback + q = @modelClass.find(args...) + if skip? and skip < 1000000 + q.skip(skip) + if limit? and limit < FETCH_LIMIT + q.limit(limit) + else + q.limit(FETCH_LIMIT) + q.exec callback # if it's not a text search but the user is an admin, let him try stuff anyway else if req.user?.isAdmin() # admins can send any sort of query down the wire diff --git a/vendor/scripts/SpriteContainer.js b/vendor/scripts/SpriteContainer.js index 49f31a26f..1a6b24c36 100644 --- a/vendor/scripts/SpriteContainer.js +++ b/vendor/scripts/SpriteContainer.js @@ -3,7 +3,7 @@ * Visit http://createjs.com/ for documentation, updates and examples. * * Copyright (c) 2010 gskinner.com, inc. -* +* * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without @@ -12,10 +12,10 @@ * copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following * conditions: -* +* * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. -* +* * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND @@ -30,71 +30,55 @@ this.createjs = this.createjs||{}; (function() { + "use strict"; -/** - * A SpriteContainer is a nestable display list that enables aggressively optimized rendering of bitmap content. - * In order to accomplish these optimizations, SpriteContainer enforces a few restrictions on its content. - * - * Restrictions: - * - only Sprite, SpriteContainer, BitmapText and DOMElement are allowed to be added as children. - * - a spriteSheet MUST be either be passed into the constructor or defined on the first child added. - * - all children (with the exception of DOMElement) MUST use the same spriteSheet. - * - *

Example

- * var data = { - * images: ["sprites.jpg"], - * frames: {width:50, height:50}, - * animations: {run:[0,4], jump:[5,8,"run"]} - * }; - * var spriteSheet = new createjs.SpriteSheet(data); - * var container = new createjs.SpriteContainer(spriteSheet); - * container.addChild(spriteInstance, spriteInstance2); - * container.x = 100; - * - * Note: SpriteContainer is not included in the minified version of EaselJS. - * - * @class SpriteContainer - * @extends Container - * @constructor - * @param {SpriteSheet} [spriteSheet] The spriteSheet to use for this SpriteContainer and its children. - **/ -var SpriteContainer = function(spriteSheet) { - this.initialize(spriteSheet); -}; -var p = SpriteContainer.prototype = new createjs.Container(); - -// public properties: /** - * The SpriteSheet that this container enforces use of. - * @property spriteSheet - * @type {SpriteSheet} - * @readonly + * A SpriteContainer is a nestable display list that enables aggressively optimized rendering of bitmap content. + * In order to accomplish these optimizations, SpriteContainer enforces a few restrictions on its content. + * + * Restrictions: + * - only Sprite, SpriteContainer, BitmapText and DOMElement are allowed to be added as children. + * - a spriteSheet MUST be either be passed into the constructor or defined on the first child added. + * - all children (with the exception of DOMElement) MUST use the same spriteSheet. + * + *

Example

+ * + * var data = { + * images: ["sprites.jpg"], + * frames: {width:50, height:50}, + * animations: {run:[0,4], jump:[5,8,"run"]} + * }; + * var spriteSheet = new createjs.SpriteSheet(data); + * var container = new createjs.SpriteContainer(spriteSheet); + * container.addChild(spriteInstance, spriteInstance2); + * container.x = 100; + * + * Note: SpriteContainer is not included in the minified version of EaselJS. + * + * @class SpriteContainer + * @extends Container + * @constructor + * @param {SpriteSheet} [spriteSheet] The spriteSheet to use for this SpriteContainer and its children. **/ - p.spriteSheet = null; - -// constructor: - - /** - * @property Container_initialize - * @type Function - * @private - **/ - p.Container_initialize = p.initialize; - - /** - * Initialization method. - * @method initialize - * @param {SpriteSheet} spriteSheet Optional. The spriteSheet to use for this SpriteContainer and its children. - * @protected - */ - p.initialize = function(spriteSheet) { - this.Container_initialize(); + function SpriteContainer(spriteSheet) { + this.Container_constructor(); + + + // public properties: + /** + * The SpriteSheet that this container enforces use of. + * @property spriteSheet + * @type {SpriteSheet} + * @readonly + **/ this.spriteSheet = spriteSheet; - }; + } + var p = createjs.extend(SpriteContainer, createjs.Container); + + // public methods: - /** * Adds a child to the top of the display list. * Only children of type SpriteContainer, Sprite, Bitmap, BitmapText, or DOMElement are allowed. @@ -127,7 +111,7 @@ var p = SpriteContainer.prototype = new createjs.Container(); * Only children of type SpriteContainer, Sprite, Bitmap, BitmapText, or DOMElement are allowed. * The child must have the same spritesheet as this container (unless it's a DOMElement). * If a spritesheet hasn't been defined, this container uses this child's spritesheet. - * + * *

Example

* addChildAt(child1, index); * @@ -163,7 +147,7 @@ var p = SpriteContainer.prototype = new createjs.Container(); } if (child._spritestage_compatibility <= 4) { var spriteSheet = child.spriteSheet; - if ((!spriteSheet || !spriteSheet._images || spriteSheet._images.length > 1) || (this.spritesheet && spritesheet !== spritesheet)) { + if ((!spriteSheet || !spriteSheet._images || spriteSheet._images.length > 1) || (this.spriteSheet && this.spriteSheet !== spriteSheet)) { console && console.log("Error: A child's spriteSheet must be equal to its parent spriteSheet and only use one image. [" + child.toString() + "]"); return child; } @@ -184,5 +168,6 @@ var p = SpriteContainer.prototype = new createjs.Container(); return "[SpriteContainer (name="+ this.name +")]"; }; -createjs.SpriteContainer = SpriteContainer; -}()); \ No newline at end of file + + createjs.SpriteContainer = createjs.promote(SpriteContainer, "Container"); +}()); diff --git a/vendor/scripts/SpriteStage.js b/vendor/scripts/SpriteStage.js index fe616c4c4..1965705aa 100644 --- a/vendor/scripts/SpriteStage.js +++ b/vendor/scripts/SpriteStage.js @@ -36,54 +36,237 @@ this.createjs = this.createjs||{}; (function() { "use strict"; -// Set which classes are compatible with SpriteStage. -// The order is important!!! If it's changed/appended, make sure that any logic that -// checks _spritestage_compatibility accounts for it! -[createjs.SpriteContainer, createjs.Sprite, createjs.BitmapText, createjs.Bitmap, createjs.DOMElement].forEach(function(_class, index) { - _class.prototype._spritestage_compatibility = index + 1; -}); -/** - * A sprite stage is the root level {{#crossLink "Container"}}{{/crossLink}} for an aggressively optimized display list. Each time its {{#crossLink "Stage/tick"}}{{/crossLink}} - * method is called, it will render its display list to its target canvas. WebGL content is fully compatible with the existing Context2D renderer. - * On devices or browsers that don't support WebGL, content will automatically be rendered via canvas 2D. - * - * Restrictions: - * - only Sprite, SpriteContainer, BitmapText, Bitmap and DOMElement are allowed to be added to the display list. - * - a child being added (with the exception of DOMElement) MUST have an image or spriteSheet defined on it. - * - a child's image/spriteSheet MUST never change while being on the display list. - * - *

Example

- * This example creates a sprite stage, adds a child to it, then uses {{#crossLink "Ticker"}}{{/crossLink}} to update the child - * and redraw the stage using {{#crossLink "SpriteStage/update"}}{{/crossLink}}. - * - * var stage = new createjs.SpriteStage("canvasElementId", false, false); - * stage.updateViewport(800, 600); - * var image = new createjs.Bitmap("imagePath.png"); - * stage.addChild(image); - * createjs.Ticker.addEventListener("tick", handleTick); - * function handleTick(event) { - * image.x += 10; - * stage.update(); - * } - * - * Note: SpriteStage is not included in the minified version of EaselJS. - * - * @class SpriteStage - * @extends Stage - * @constructor - * @param {HTMLCanvasElement | String | Object} canvas A canvas object that the SpriteStage will render to, or the string id - * of a canvas object in the current document. - * @param {Boolean} preserveDrawingBuffer If true, the canvas is NOT auto-cleared by WebGL (spec discourages true). Useful if you want to use p.autoClear = false. - * @param {Boolean} antialias Specifies whether or not the browser's WebGL implementation should try to perform antialiasing. - **/ -var SpriteStage = function(canvas, preserveDrawingBuffer, antialias) { - this.initialize(canvas, preserveDrawingBuffer, antialias); -}; -var p = SpriteStage.prototype = new createjs.Stage(); + // Set which classes are compatible with SpriteStage. + // The order is important!!! If it's changed/appended, make sure that any logic that + // checks _spritestage_compatibility accounts for it! + [createjs.SpriteContainer, createjs.Sprite, createjs.BitmapText, createjs.Bitmap, createjs.DOMElement].forEach(function(_class, index) { + _class.prototype._spritestage_compatibility = index + 1; + }); + -// static properties: +// constructor: + /** + * A sprite stage is the root level {{#crossLink "Container"}}{{/crossLink}} for an aggressively optimized display list. Each time its {{#crossLink "Stage/tick"}}{{/crossLink}} + * method is called, it will render its display list to its target canvas. WebGL content is fully compatible with the existing Context2D renderer. + * On devices or browsers that don't support WebGL, content will automatically be rendered via canvas 2D. + * + * Restrictions: + * - only Sprite, SpriteContainer, BitmapText, Bitmap and DOMElement are allowed to be added to the display list. + * - a child being added (with the exception of DOMElement) MUST have an image or spriteSheet defined on it. + * - a child's image/spriteSheet MUST never change while being on the display list. + * + *

Example

+ * This example creates a sprite stage, adds a child to it, then uses {{#crossLink "Ticker"}}{{/crossLink}} to update the child + * and redraw the stage using {{#crossLink "SpriteStage/update"}}{{/crossLink}}. + * + * var stage = new createjs.SpriteStage("canvasElementId", false, false); + * stage.updateViewport(800, 600); + * var image = new createjs.Bitmap("imagePath.png"); + * stage.addChild(image); + * createjs.Ticker.addEventListener("tick", handleTick); + * function handleTick(event) { + * image.x += 10; + * stage.update(); + * } + * + * Note: SpriteStage is not included in the minified version of EaselJS. + * + * @class SpriteStage + * @extends Stage + * @constructor + * @param {HTMLCanvasElement | String | Object} canvas A canvas object that the SpriteStage will render to, or the string id + * of a canvas object in the current document. + * @param {Boolean} preserveDrawingBuffer If true, the canvas is NOT auto-cleared by WebGL (spec discourages true). Useful if you want to use p.autoClear = false. + * @param {Boolean} antialias Specifies whether or not the browser's WebGL implementation should try to perform antialiasing. + **/ + function SpriteStage(canvas, preserveDrawingBuffer, antialias) { + this.Stage_constructor(canvas); + + + // private properties: + /** + * Specifies whether or not the canvas is auto-cleared by WebGL. Spec discourages true. + * If true, the canvas is NOT auto-cleared by WebGL. Value is ignored if `_alphaEnabled` is false. + * Useful if you want to use `autoClear = false`. + * @property _preserveDrawingBuffer + * @protected + * @type {Boolean} + * @default false + **/ + this._preserveDrawingBuffer = preserveDrawingBuffer||false; + + /** + * Specifies whether or not the browser's WebGL implementation should try to perform antialiasing. + * @property _antialias + * @protected + * @type {Boolean} + * @default false + **/ + this._antialias = antialias||false; + + /** + * The width of the canvas element. + * @property _viewportWidth + * @protected + * @type {Number} + * @default 0 + **/ + this._viewportWidth = 0; + + /** + * The height of the canvas element. + * @property _viewportHeight + * @protected + * @type {Number} + * @default 0 + **/ + this._viewportHeight = 0; + + /** + * A 2D projection matrix used to convert WebGL's clipspace into normal pixels. + * @property _projectionMatrix + * @protected + * @type {Float32Array} + * @default null + **/ + this._projectionMatrix = null; + + /** + * The current WebGL canvas context. + * @property _webGLContext + * @protected + * @type {WebGLRenderingContext} + * @default null + **/ + this._webGLContext = null; + + /** + * Indicates whether or not an error has been detected when dealing with WebGL. + * If the is true, the behavior should be to use Canvas 2D rendering instead. + * @property _webGLErrorDetected + * @protected + * @type {Boolean} + * @default false + **/ + this._webGLErrorDetected = false; + + /** + * The color to use when the WebGL canvas has been cleared. + * @property _clearColor + * @protected + * @type {Object} + * @default null + **/ + this._clearColor = null; + + /** + * The maximum number of textures WebGL can work with per draw call. + * @property _maxTexturesPerDraw + * @protected + * @type {Number} + * @default 1 + **/ + this._maxTexturesPerDraw = 1; // TODO: this is currently unused. + + /** + * The maximum total number of boxes points that can be defined per draw call. + * @property _maxBoxesPointsPerDraw + * @protected + * @type {Number} + * @default null + **/ + this._maxBoxesPointsPerDraw = null; + + /** + * The maximum number of boxes (sprites) that can be drawn in one draw call. + * @property _maxBoxesPerDraw + * @protected + * @type {Number} + * @default null + **/ + this._maxBoxesPerDraw = null; + + /** + * The maximum number of indices that can be drawn in one draw call. + * @property _maxIndicesPerDraw + * @protected + * @type {Number} + * @default null + **/ + this._maxIndicesPerDraw = null; + + /** + * The shader program used to draw everything. + * @property _shaderProgram + * @protected + * @type {WebGLProgram} + * @default null + **/ + this._shaderProgram = null; + + /** + * The vertices data for the current draw call. + * @property _vertices + * @protected + * @type {Float32Array} + * @default null + **/ + this._vertices = null; + + /** + * The buffer that contains all the vertices data. + * @property _verticesBuffer + * @protected + * @type {WebGLBuffer} + * @default null + **/ + this._verticesBuffer = null; + + /** + * The indices to the vertices defined in this._vertices. + * @property _indices + * @protected + * @type {Uint16Array} + * @default null + **/ + this._indices = null; + + /** + * The buffer that contains all the indices data. + * @property _indicesBuffer + * @protected + * @type {WebGLBuffer} + * @default null + **/ + this._indicesBuffer = null; + + /** + * The current box index being defined for drawing. + * @property _currentBoxIndex + * @protected + * @type {Number} + * @default -1 + **/ + this._currentBoxIndex = -1; + + /** + * The current texture that will be used to draw into the GPU. + * @property _drawTexture + * @protected + * @type {WebGLTexture} + * @default null + **/ + this._drawTexture = null; + + + // setup: + this._initializeWebGL(); + } + var p = createjs.extend(SpriteStage, createjs.Stage); + +// constants: /** * The number of properties defined per vertex in p._verticesBuffer. * x, y, textureU, textureV, alpha @@ -149,7 +332,8 @@ var p = SpriteStage.prototype = new createjs.Stage(); * @readonly **/ SpriteStage.MAX_BOXES_POINTS_INCREMENT = SpriteStage.MAX_INDEX_SIZE / 4; - + + // getter / setters: /** * Indicates whether WebGL is being used for rendering. For example, this would be false if WebGL is not @@ -168,209 +352,8 @@ var p = SpriteStage.prototype = new createjs.Stage(); }); } catch (e) {} // TODO: use Log -// private properties: - - /** - * Specifies whether or not the canvas is auto-cleared by WebGL. Spec discourages true. - * If true, the canvas is NOT auto-cleared by WebGL. Value is ignored if p._alphaEnabled is false. - * Useful if you want to use p.autoClear = false. - * @property _preserveDrawingBuffer - * @protected - * @type {Boolean} - * @default false - **/ - p._preserveDrawingBuffer = false; - - /** - * Specifies whether or not the browser's WebGL implementation should try to perform antialiasing. - * @property _antialias - * @protected - * @type {Boolean} - * @default false - **/ - p._antialias = false; - - /** - * The width of the canvas element. - * @property _viewportWidth - * @protected - * @type {Number} - * @default 0 - **/ - p._viewportWidth = 0; - - /** - * The height of the canvas element. - * @property _viewportHeight - * @protected - * @type {Number} - * @default 0 - **/ - p._viewportHeight = 0; - - /** - * A 2D projection matrix used to convert WebGL's clipspace into normal pixels. - * @property _projectionMatrix - * @protected - * @type {Float32Array} - * @default null - **/ - p._projectionMatrix = null; - - /** - * The current WebGL canvas context. - * @property _webGLContext - * @protected - * @type {WebGLRenderingContext} - * @default null - **/ - p._webGLContext = null; - - /** - * Indicates whether or not an error has been detected when dealing with WebGL. - * If the is true, the behavior should be to use Canvas 2D rendering instead. - * @property _webGLErrorDetected - * @protected - * @type {Boolean} - * @default false - **/ - p._webGLErrorDetected = false; - - /** - * The color to use when the WebGL canvas has been cleared. - * @property _clearColor - * @protected - * @type {Object} - * @default null - **/ - p._clearColor = null; - - /** - * The maximum number of textures WebGL can work with per draw call. - * @property _maxTexturesPerDraw - * @protected - * @type {Number} - * @default 1 - **/ - p._maxTexturesPerDraw = 1; - - /** - * The maximum total number of boxes points that can be defined per draw call. - * @property _maxBoxesPointsPerDraw - * @protected - * @type {Number} - * @default null - **/ - p._maxBoxesPointsPerDraw = null; - - /** - * The maximum number of boxes (sprites) that can be drawn in one draw call. - * @property _maxBoxesPerDraw - * @protected - * @type {Number} - * @default null - **/ - p._maxBoxesPerDraw = null; - - /** - * The maximum number of indices that can be drawn in one draw call. - * @property _maxIndicesPerDraw - * @protected - * @type {Number} - * @default null - **/ - p._maxIndicesPerDraw = null; - - /** - * The shader program used to draw everything. - * @property _shaderProgram - * @protected - * @type {WebGLProgram} - * @default null - **/ - p._shaderProgram = null; - - /** - * The vertices data for the current draw call. - * @property _vertices - * @protected - * @type {Float32Array} - * @default null - **/ - p._vertices = null; - - /** - * The buffer that contains all the vertices data. - * @property _verticesBuffer - * @protected - * @type {WebGLBuffer} - * @default null - **/ - p._verticesBuffer = null; - - /** - * The indices to the vertices defined in p._vertices. - * @property _indices - * @protected - * @type {Uint16Array} - * @default null - **/ - p._indices = null; - - /** - * The buffer that contains all the indices data. - * @property _indicesBuffer - * @protected - * @type {WebGLBuffer} - * @default null - **/ - p._indicesBuffer = null; - - /** - * The current box index being defined for drawing. - * @property _currentBoxIndex - * @protected - * @type {Number} - * @default -1 - **/ - p._currentBoxIndex = -1; - - /** - * The current texture that will be used to draw into the GPU. - * @property _drawTexture - * @protected - * @type {WebGLTexture} - * @default null - **/ - p._drawTexture = null; - -// constructor: - - /** - * @property Stage_initialize - * @type Function - * @private - **/ - p.Stage_initialize = p.initialize; - - /** - * Initialization method. - * @method initialize - * @param {HTMLCanvasElement | String | Object} canvas A canvas object, or the string id of a canvas object in the current document. - * @param {Boolean} preserveDrawingBuffer If true, the canvas is NOT auto-cleared by WebGL (spec discourages true). Useful if you want to use p.autoClear = false. - * @param {Boolean} antialias Specifies whether or not the browser's WebGL implementation should try to perform antialiasing. - * @protected - **/ - p.initialize = function(canvas, preserveDrawingBuffer, antialias) { - this._preserveDrawingBuffer = preserveDrawingBuffer !== undefined ? preserveDrawingBuffer : this._preserveDrawingBuffer; - this._antialias = antialias !== undefined ? antialias : this._antialias; - - this.Stage_initialize(canvas); - this._initializeWebGL(); - }; // public methods: - /** * Adds a child to the top of the display list. * Only children of type SpriteContainer, Sprite, Bitmap, BitmapText, or DOMElement are allowed. @@ -403,6 +386,7 @@ var p = SpriteStage.prototype = new createjs.Stage(); * Children also MUST have either an image or spriteSheet defined on them (unless it's a DOMElement). * *

Example

+ * * addChildAt(child1, index); * * You can also add multiple children, such as: @@ -432,7 +416,6 @@ var p = SpriteStage.prototype = new createjs.Stage(); if (child._spritestage_compatibility >= 1) { // The child is compatible with SpriteStage. } else { - console.trace(); console && console.log("Error: You can only add children of type SpriteContainer, Sprite, Bitmap, BitmapText, or DOMElement. [" + child.toString() + "]"); return child; } @@ -447,32 +430,10 @@ var p = SpriteStage.prototype = new createjs.Stage(); return child; }; - /** - * Each time the update method is called, the stage will tick all descendants (see: {{#crossLink "DisplayObject/tick"}}{{/crossLink}}) - * and then render the display list to the canvas using WebGL. If WebGL is not supported in the browser, it will default to a 2D context. - * - * Any parameters passed to `update()` will be passed on to any - * {{#crossLink "DisplayObject/tick:event"}}{{/crossLink}} event handlers. - * - * Some time-based features in EaselJS (for example {{#crossLink "Sprite/framerate"}}{{/crossLink}} require that - * a tick event object (or equivalent) be passed as the first parameter to update(). For example: - * - * Ticker.addEventListener("tick", handleTick); - * function handleTick(evtObj) { - * // do some work here, then update the stage, passing through the event object: - * myStage.update(evtObj); - * } - * - * @method update - * @param {*} [params]* Params to include when ticking descendants. The first param should usually be a tick event. - **/ - p.update = function(params) { + /** docced in super class **/ + p.update = function(props) { if (!this.canvas) { return; } - if (this.tickOnUpdate) { - this.dispatchEvent("tickstart"); // TODO: make cancellable? - this._tick((arguments.length ? arguments : null)); - this.dispatchEvent("tickend"); - } + if (this.tickOnUpdate) { this.tick(props); } this.dispatchEvent("drawstart"); // TODO: make cancellable? if (this.autoClear) { this.clear(); } var ctx = this._setWebGLContext(); @@ -508,13 +469,6 @@ var p = SpriteStage.prototype = new createjs.Stage(); } }; - /** - * @property Stage_draw - * @type {Function} - * @private - **/ - p.Stage_draw = p.draw; - /** * Draws the stage into the specified context (using WebGL) ignoring its visible, alpha, shadow, and transform. * If WebGL is not supported in the browser, it will default to a 2D context. @@ -881,11 +835,10 @@ var p = SpriteStage.prototype = new createjs.Stage(); for (var i = 0, l = kids.length; i < l; i++) { kid = kids[i]; if (!kid.isVisible()) { continue; } - mtx = kid._matrix; + mtx = kid._props.matrix; // Get the kid's global matrix (relative to the stage): - mtx = (parentMVMatrix ? mtx.copy(parentMVMatrix) : mtx.identity()) - .appendTransform(kid.x, kid.y, kid.scaleX, kid.scaleY, kid.rotation, kid.skewX, kid.skewY, kid.regX, kid.regY); + mtx = (parentMVMatrix ? mtx.copy(parentMVMatrix) : mtx.identity()).prependTransform(kid.x, kid.y, kid.scaleX, kid.scaleY, kid.rotation, kid.skewX, kid.skewY, kid.regX, kid.regY); // Set default texture coordinates: var uStart = 0, uEnd = 1, @@ -1027,5 +980,6 @@ var p = SpriteStage.prototype = new createjs.Stage(); this._drawTexture = null; }; -createjs.SpriteStage = SpriteStage; + + createjs.SpriteStage = createjs.promote(SpriteStage, "Stage"); }()); diff --git a/vendor/scripts/easeljs-NEXT.combined.js b/vendor/scripts/easeljs-NEXT.combined.js index 9e51cf98c..9bbe6a3eb 100644 --- a/vendor/scripts/easeljs-NEXT.combined.js +++ b/vendor/scripts/easeljs-NEXT.combined.js @@ -1,202 +1,278 @@ -/* -* Event -* Visit http://createjs.com/ for documentation, updates and examples. -* -* Copyright (c) 2010 gskinner.com, inc. -* -* Permission is hereby granted, free of charge, to any person -* obtaining a copy of this software and associated documentation -* files (the "Software"), to deal in the Software without -* restriction, including without limitation the rights to use, -* copy, modify, merge, publish, distribute, sublicense, and/or sell -* copies of the Software, and to permit persons to whom the -* Software is furnished to do so, subject to the following -* conditions: -* -* The above copyright notice and this permission notice shall be -* included in all copies or substantial portions of the Software. -* -* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES -* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT -* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR -* OTHER DEALINGS IN THE SOFTWARE. -*/ + + +//############################################################################## +// extend.js +//############################################################################## + +this.createjs = this.createjs||{}; /** - * A collection of Classes that are shared across all the CreateJS libraries. The classes are included in the minified - * files of each library and are available on the createsjs namespace directly. - * - *

Example

- * myObject.addEventListener("change", createjs.proxy(myMethod, scope)); - * - * @module CreateJS - * @main CreateJS + * @class Utility Methods */ -// namespace: +/** + * Sets up the prototype chain and constructor property for a new class. + * + * This should be called right after creating the class constructor. + * + * function MySubClass() {} + * createjs.extend(MySubClass, MySuperClass); + * ClassB.prototype.doSomething = function() { } + * + * var foo = new MySubClass(); + * console.log(foo instanceof MySuperClass); // true + * console.log(foo.prototype.constructor === MySubClass); // true + * + * @method extends + * @param {Function} subclass The subclass. + * @param {Function} superclass The superclass to extend. + * @return {Function} Returns the subclass's new prototype. + */ +createjs.extend = function(subclass, superclass) { + "use strict"; + + function o() { this.constructor = subclass; } + o.prototype = superclass.prototype; + return (subclass.prototype = new o()); +}; + +//############################################################################## +// promote.js +//############################################################################## + +this.createjs = this.createjs||{}; + +/** + * @class Utility Methods + */ + +/** + * Promotes any methods on the super class that were overridden, by creating an alias in the format `prefix_methodName`. + * It is recommended to use the super class's name as the prefix. + * An alias to the super class's constructor is always added in the format `prefix_constructor`. + * This allows the subclass to call super class methods without using `function.call`, providing better performance. + * + * For example, if `MySubClass` extends `MySuperClass`, and both define a `draw` method, then calling `promote(MySubClass, "MySuperClass")` + * would add a `MySuperClass_constructor` method to MySubClass and promote the `draw` method on `MySuperClass` to the + * prototype of `MySubClass` as `MySuperClass_draw`. + * + * This should be called after the class's prototype is fully defined. + * + * function ClassA(name) { + * this.name = name; + * } + * ClassA.prototype.greet = function() { + * return "Hello "+this.name; + * } + * + * function ClassB(name, punctuation) { + * this.ClassA_constructor(name); + * this.punctuation = punctuation; + * } + * createjs.extend(ClassB, ClassA); + * ClassB.prototype.greet = function() { + * return this.ClassA_greet()+this.punctuation; + * } + * createjs.promote(ClassB, "ClassA"); + * + * var foo = new ClassB("World", "!?!"); + * console.log(foo.greet()); // Hello World!?! + * + * @method promote + * @param {Function} subclass The class to promote super class methods on. + * @param {String} prefix The prefix to add to the promoted method names. Usually the name of the superclass. + * @return {Function} Returns the subclass. + */ +createjs.promote = function(subclass, prefix) { + "use strict"; + + var subP = subclass.prototype, supP = (Object.getPrototypeOf&&Object.getPrototypeOf(subP))||subP.__proto__; + if (supP) { + subP[(prefix+="_") + "constructor"] = supP.constructor; // constructor is not always innumerable + for (var n in supP) { + if (subP.hasOwnProperty(n) && (typeof supP[n] == "function")) { subP[prefix + n] = supP[n]; } + } + } + return subclass; +}; + +//############################################################################## +// indexOf.js +//############################################################################## + +this.createjs = this.createjs||{}; + +/** + * @class Utility Methods + */ + +/** + * Finds the first occurrence of a specified value searchElement in the passed in array, and returns the index of + * that value. Returns -1 if value is not found. + * + * var i = createjs.indexOf(myArray, myElementToFind); + * + * @method indexOf + * @param {Array} array Array to search for searchElement + * @param searchElement Element to find in array. + * @return {Number} The first index of searchElement in array. + */ +createjs.indexOf = function (array, searchElement){ + "use strict"; + + for (var i = 0,l=array.length; i < l; i++) { + if (searchElement === array[i]) { + return i; + } + } + return -1; +}; + +//############################################################################## +// Event.js +//############################################################################## + this.createjs = this.createjs||{}; (function() { "use strict"; -/** - * Contains properties and methods shared by all events for use with - * {{#crossLink "EventDispatcher"}}{{/crossLink}}. - * - * Note that Event objects are often reused, so you should never - * rely on an event object's state outside of the call stack it was received in. - * @class Event - * @param {String} type The event type. - * @param {Boolean} bubbles Indicates whether the event will bubble through the display list. - * @param {Boolean} cancelable Indicates whether the default behaviour of this event can be cancelled. - * @constructor - **/ -var Event = function(type, bubbles, cancelable) { - this.initialize(type, bubbles, cancelable); -}; -var p = Event.prototype; - -// events: - -// public properties: - - /** - * The type of event. - * @property type - * @type String - **/ - p.type = null; - - /** - * The object that generated an event. - * @property target - * @type Object - * @default null - * @readonly - */ - p.target = null; - - /** - * The current target that a bubbling event is being dispatched from. For non-bubbling events, this will - * always be the same as target. For example, if childObj.parent = parentObj, and a bubbling event - * is generated from childObj, then a listener on parentObj would receive the event with - * target=childObj (the original target) and currentTarget=parentObj (where the listener was added). - * @property currentTarget - * @type Object - * @default null - * @readonly - */ - p.currentTarget = null; - - /** - * For bubbling events, this indicates the current event phase:
    - *
  1. capture phase: starting from the top parent to the target
  2. - *
  3. at target phase: currently being dispatched from the target
  4. - *
  5. bubbling phase: from the target to the top parent
  6. - *
- * @property eventPhase - * @type Number - * @default 0 - * @readonly - */ - p.eventPhase = 0; - - /** - * Indicates whether the event will bubble through the display list. - * @property bubbles - * @type Boolean - * @default false - * @readonly - */ - p.bubbles = false; - - /** - * Indicates whether the default behaviour of this event can be cancelled via - * {{#crossLink "Event/preventDefault"}}{{/crossLink}}. This is set via the Event constructor. - * @property cancelable - * @type Boolean - * @default false - * @readonly - */ - p.cancelable = false; - - /** - * The epoch time at which this event was created. - * @property timeStamp - * @type Number - * @default 0 - * @readonly - */ - p.timeStamp = 0; - - /** - * Indicates if {{#crossLink "Event/preventDefault"}}{{/crossLink}} has been called - * on this event. - * @property defaultPrevented - * @type Boolean - * @default false - * @readonly - */ - p.defaultPrevented = false; - - /** - * Indicates if {{#crossLink "Event/stopPropagation"}}{{/crossLink}} or - * {{#crossLink "Event/stopImmediatePropagation"}}{{/crossLink}} has been called on this event. - * @property propagationStopped - * @type Boolean - * @default false - * @readonly - */ - p.propagationStopped = false; - - /** - * Indicates if {{#crossLink "Event/stopImmediatePropagation"}}{{/crossLink}} has been called - * on this event. - * @property immediatePropagationStopped - * @type Boolean - * @default false - * @readonly - */ - p.immediatePropagationStopped = false; - - /** - * Indicates if {{#crossLink "Event/remove"}}{{/crossLink}} has been called on this event. - * @property removed - * @type Boolean - * @default false - * @readonly - */ - p.removed = false; - // constructor: /** - * Initialization method. - * @method initialize + * Contains properties and methods shared by all events for use with + * {{#crossLink "EventDispatcher"}}{{/crossLink}}. + * + * Note that Event objects are often reused, so you should never + * rely on an event object's state outside of the call stack it was received in. + * @class Event * @param {String} type The event type. * @param {Boolean} bubbles Indicates whether the event will bubble through the display list. * @param {Boolean} cancelable Indicates whether the default behaviour of this event can be cancelled. - * @protected + * @constructor **/ - p.initialize = function(type, bubbles, cancelable) { + function Event(type, bubbles, cancelable) { + + + // public properties: + /** + * The type of event. + * @property type + * @type String + **/ this.type = type; - this.bubbles = bubbles; - this.cancelable = cancelable; + + /** + * The object that generated an event. + * @property target + * @type Object + * @default null + * @readonly + */ + this.target = null; + + /** + * The current target that a bubbling event is being dispatched from. For non-bubbling events, this will + * always be the same as target. For example, if childObj.parent = parentObj, and a bubbling event + * is generated from childObj, then a listener on parentObj would receive the event with + * target=childObj (the original target) and currentTarget=parentObj (where the listener was added). + * @property currentTarget + * @type Object + * @default null + * @readonly + */ + this.currentTarget = null; + + /** + * For bubbling events, this indicates the current event phase:
    + *
  1. capture phase: starting from the top parent to the target
  2. + *
  3. at target phase: currently being dispatched from the target
  4. + *
  5. bubbling phase: from the target to the top parent
  6. + *
+ * @property eventPhase + * @type Number + * @default 0 + * @readonly + */ + this.eventPhase = 0; + + /** + * Indicates whether the event will bubble through the display list. + * @property bubbles + * @type Boolean + * @default false + * @readonly + */ + this.bubbles = !!bubbles; + + /** + * Indicates whether the default behaviour of this event can be cancelled via + * {{#crossLink "Event/preventDefault"}}{{/crossLink}}. This is set via the Event constructor. + * @property cancelable + * @type Boolean + * @default false + * @readonly + */ + this.cancelable = !!cancelable; + + /** + * The epoch time at which this event was created. + * @property timeStamp + * @type Number + * @default 0 + * @readonly + */ this.timeStamp = (new Date()).getTime(); - }; + + /** + * Indicates if {{#crossLink "Event/preventDefault"}}{{/crossLink}} has been called + * on this event. + * @property defaultPrevented + * @type Boolean + * @default false + * @readonly + */ + this.defaultPrevented = false; + + /** + * Indicates if {{#crossLink "Event/stopPropagation"}}{{/crossLink}} or + * {{#crossLink "Event/stopImmediatePropagation"}}{{/crossLink}} has been called on this event. + * @property propagationStopped + * @type Boolean + * @default false + * @readonly + */ + this.propagationStopped = false; + + /** + * Indicates if {{#crossLink "Event/stopImmediatePropagation"}}{{/crossLink}} has been called + * on this event. + * @property immediatePropagationStopped + * @type Boolean + * @default false + * @readonly + */ + this.immediatePropagationStopped = false; + + /** + * Indicates if {{#crossLink "Event/remove"}}{{/crossLink}} has been called on this event. + * @property removed + * @type Boolean + * @default false + * @readonly + */ + this.removed = false; + } + var p = Event.prototype; + // public methods: - /** * Sets {{#crossLink "Event/defaultPrevented"}}{{/crossLink}} to true. * Mirrors the DOM event standard. * @method preventDefault **/ p.preventDefault = function() { - this.defaultPrevented = true; + this.defaultPrevented = this.cancelable&&true; }; /** @@ -240,6 +316,18 @@ var p = Event.prototype; p.clone = function() { return new Event(this.type, this.bubbles, this.cancelable); }; + + /** + * Provides a chainable shortcut method for setting a number of properties on the instance. + * + * @method set + * @param {Object} props A generic object containing properties to copy to the instance. + * @return {Event} Returns the instance the method is called on (useful for chaining calls.) + */ + p.set = function(props) { + for (var n in props) { this[n] = props[n]; } + return this; + }; /** * Returns a string representation of this object. @@ -250,101 +338,91 @@ var p = Event.prototype; return "[Event (type="+this.type+")]"; }; -createjs.Event = Event; + createjs.Event = Event; }()); -/* -* EventDispatcher -* Visit http://createjs.com/ for documentation, updates and examples. -* -* Copyright (c) 2010 gskinner.com, inc. -* -* Permission is hereby granted, free of charge, to any person -* obtaining a copy of this software and associated documentation -* files (the "Software"), to deal in the Software without -* restriction, including without limitation the rights to use, -* copy, modify, merge, publish, distribute, sublicense, and/or sell -* copies of the Software, and to permit persons to whom the -* Software is furnished to do so, subject to the following -* conditions: -* -* The above copyright notice and this permission notice shall be -* included in all copies or substantial portions of the Software. -* -* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES -* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT -* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR -* OTHER DEALINGS IN THE SOFTWARE. -*/ -/** - * @module CreateJS - */ +//############################################################################## +// EventDispatcher.js +//############################################################################## -// namespace: this.createjs = this.createjs||{}; (function() { "use strict"; -/** - * EventDispatcher provides methods for managing queues of event listeners and dispatching events. - * - * You can either extend EventDispatcher or mix its methods into an existing prototype or instance by using the - * EventDispatcher {{#crossLink "EventDispatcher/initialize"}}{{/crossLink}} method. - * - * Together with the CreateJS Event class, EventDispatcher provides an extended event model that is based on the - * DOM Level 2 event model, including addEventListener, removeEventListener, and dispatchEvent. It supports - * bubbling / capture, preventDefault, stopPropagation, stopImmediatePropagation, and handleEvent. - * - * EventDispatcher also exposes a {{#crossLink "EventDispatcher/on"}}{{/crossLink}} method, which makes it easier - * to create scoped listeners, listeners that only run once, and listeners with associated arbitrary data. The - * {{#crossLink "EventDispatcher/off"}}{{/crossLink}} method is merely an alias to - * {{#crossLink "EventDispatcher/removeEventListener"}}{{/crossLink}}. - * - * Another addition to the DOM Level 2 model is the {{#crossLink "EventDispatcher/removeAllEventListeners"}}{{/crossLink}} - * method, which can be used to listeners for all events, or listeners for a specific event. The Event object also - * includes a {{#crossLink "Event/remove"}}{{/crossLink}} method which removes the active listener. - * - *

Example

- * Add EventDispatcher capabilities to the "MyClass" class. - * - * EventDispatcher.initialize(MyClass.prototype); - * - * Add an event (see {{#crossLink "EventDispatcher/addEventListener"}}{{/crossLink}}). - * - * instance.addEventListener("eventName", handlerMethod); - * function handlerMethod(event) { - * console.log(event.target + " Was Clicked"); - * } - * - * Maintaining proper scope
- * Scope (ie. "this") can be be a challenge with events. Using the {{#crossLink "EventDispatcher/on"}}{{/crossLink}} - * method to subscribe to events simplifies this. - * - * instance.addEventListener("click", function(event) { - * console.log(instance == this); // false, scope is ambiguous. - * }); - * - * instance.on("click", function(event) { - * console.log(instance == this); // true, "on" uses dispatcher scope by default. - * }); - * - * If you want to use addEventListener instead, you may want to use function.bind() or a similar proxy to manage scope. - * - * - * @class EventDispatcher - * @constructor - **/ -var EventDispatcher = function() { -/* this.initialize(); */ // not needed. -}; -var p = EventDispatcher.prototype; + +// constructor: + /** + * EventDispatcher provides methods for managing queues of event listeners and dispatching events. + * + * You can either extend EventDispatcher or mix its methods into an existing prototype or instance by using the + * EventDispatcher {{#crossLink "EventDispatcher/initialize"}}{{/crossLink}} method. + * + * Together with the CreateJS Event class, EventDispatcher provides an extended event model that is based on the + * DOM Level 2 event model, including addEventListener, removeEventListener, and dispatchEvent. It supports + * bubbling / capture, preventDefault, stopPropagation, stopImmediatePropagation, and handleEvent. + * + * EventDispatcher also exposes a {{#crossLink "EventDispatcher/on"}}{{/crossLink}} method, which makes it easier + * to create scoped listeners, listeners that only run once, and listeners with associated arbitrary data. The + * {{#crossLink "EventDispatcher/off"}}{{/crossLink}} method is merely an alias to + * {{#crossLink "EventDispatcher/removeEventListener"}}{{/crossLink}}. + * + * Another addition to the DOM Level 2 model is the {{#crossLink "EventDispatcher/removeAllEventListeners"}}{{/crossLink}} + * method, which can be used to listeners for all events, or listeners for a specific event. The Event object also + * includes a {{#crossLink "Event/remove"}}{{/crossLink}} method which removes the active listener. + * + *

Example

+ * Add EventDispatcher capabilities to the "MyClass" class. + * + * EventDispatcher.initialize(MyClass.prototype); + * + * Add an event (see {{#crossLink "EventDispatcher/addEventListener"}}{{/crossLink}}). + * + * instance.addEventListener("eventName", handlerMethod); + * function handlerMethod(event) { + * console.log(event.target + " Was Clicked"); + * } + * + * Maintaining proper scope
+ * Scope (ie. "this") can be be a challenge with events. Using the {{#crossLink "EventDispatcher/on"}}{{/crossLink}} + * method to subscribe to events simplifies this. + * + * instance.addEventListener("click", function(event) { + * console.log(instance == this); // false, scope is ambiguous. + * }); + * + * instance.on("click", function(event) { + * console.log(instance == this); // true, "on" uses dispatcher scope by default. + * }); + * + * If you want to use addEventListener instead, you may want to use function.bind() or a similar proxy to manage scope. + * + * + * @class EventDispatcher + * @constructor + **/ + function EventDispatcher() { + + + // private properties: + /** + * @protected + * @property _listeners + * @type Object + **/ + this._listeners = null; + + /** + * @protected + * @property _captureListeners + * @type Object + **/ + this._captureListeners = null; + } + var p = EventDispatcher.prototype; +// static public methods: /** * Static initializer to mix EventDispatcher methods into a target object or prototype. * @@ -367,30 +445,6 @@ var p = EventDispatcher.prototype; target.willTrigger = p.willTrigger; }; -// constructor: - -// private properties: - /** - * @protected - * @property _listeners - * @type Object - **/ - p._listeners = null; - - /** - * @protected - * @property _captureListeners - * @type Object - **/ - p._captureListeners = null; - -// constructor: - /** - * Initialization method. - * @method initialize - * @protected - **/ - p.initialize = function() {}; // public methods: /** @@ -547,19 +601,19 @@ var p = EventDispatcher.prototype; * @param {Object | String | Event} eventObj An object with a "type" property, or a string type. * While a generic object will work, it is recommended to use a CreateJS Event instance. If a string is used, * dispatchEvent will construct an Event instance with the specified type. - * @param {Object} [target] The object to use as the target property of the event object. This will default to the - * dispatching object. This parameter is deprecated and will be removed. * @return {Boolean} Returns the value of eventObj.defaultPrevented. **/ - p.dispatchEvent = function(eventObj, target) { + p.dispatchEvent = function(eventObj) { if (typeof eventObj == "string") { // won't bubble, so skip everything if there's no listeners: var listeners = this._listeners; if (!listeners || !listeners[eventObj]) { return false; } eventObj = new createjs.Event(eventObj); + } else if (eventObj.target && eventObj.clone) { + // redispatching an active event object, so clone it: + eventObj = eventObj.clone(); } - // TODO: deprecated. Target param is deprecated, only use case is MouseEvent/mousemove, remove. - try { eventObj.target = target||this; } catch (e) {} // allows redispatching of native events + try { eventObj.target = this; } catch (e) {} // try/catch allows redispatching of native events if (!eventObj.bubbles || !this.parent) { this._dispatchEvent(eventObj, 2); @@ -619,6 +673,7 @@ var p = EventDispatcher.prototype; return "[EventDispatcher]"; }; + // private methods: /** * @method _dispatchEvent @@ -634,6 +689,7 @@ var p = EventDispatcher.prototype; try { eventObj.currentTarget = this; } catch (e) {} try { eventObj.eventPhase = eventPhase; } catch (e) {} eventObj.removed = false; + arr = arr.slice(); // to avoid issues with items being removed or added during the dispatch for (var i=0; iUID.get()) - * and should not be instantiated. - * @class UID - * @static - **/ -var UID = function() { - throw "UID cannot be instantiated"; -} - - /** - * @property _nextID - * @type Number - * @protected - **/ - UID._nextID = 0; - - /** - * Returns the next unique id. - * @method get - * @return {Number} The next unique id - * @static - **/ - UID.get = function() { - return UID._nextID++; - } - -createjs.UID = UID; -}()); -/* -* Ticker -* Visit http://createjs.com/ for documentation, updates and examples. -* -* Copyright (c) 2010 gskinner.com, inc. -* -* Permission is hereby granted, free of charge, to any person -* obtaining a copy of this software and associated documentation -* files (the "Software"), to deal in the Software without -* restriction, including without limitation the rights to use, -* copy, modify, merge, publish, distribute, sublicense, and/or sell -* copies of the Software, and to permit persons to whom the -* Software is furnished to do so, subject to the following -* conditions: -* -* The above copyright notice and this permission notice shall be -* included in all copies or substantial portions of the Software. -* -* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES -* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT -* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR -* OTHER DEALINGS IN THE SOFTWARE. -*/ - -/** - * @module EaselJS - */ - -// namespace: -this.createjs = this.createjs||{}; - -(function() { - "use strict"; // constructor: -/** - * The Ticker provides a centralized tick or heartbeat broadcast at a set interval. Listeners can subscribe to the tick - * event to be notified when a set time interval has elapsed. - * - * Note that the interval that the tick event is called is a target interval, and may be broadcast at a slower interval - * during times of high CPU load. The Ticker class uses a static interface (ex. Ticker.getPaused()) and - * should not be instantiated. - * - *

Example

- * createjs.Ticker.addEventListener("tick", handleTick); - * function handleTick(event) { - * // Actions carried out each frame - * if (!event.paused) { - * // Actions carried out when the Ticker is not paused. - * } - * } - * - * To update a stage every tick, the {{#crossLink "Stage"}}{{/crossLink}} instance can also be used as a listener, as - * it will automatically update when it receives a tick event: - * - * createjs.Ticker.addEventListener("tick", stage); - * - * @class Ticker - * @uses EventDispatcher - * @static - **/ -var Ticker = function() { - throw "Ticker cannot be instantiated."; -}; + /** + * The Ticker provides a centralized tick or heartbeat broadcast at a set interval. Listeners can subscribe to the tick + * event to be notified when a set time interval has elapsed. + * + * Note that the interval that the tick event is called is a target interval, and may be broadcast at a slower interval + * during times of high CPU load. The Ticker class uses a static interface (ex. Ticker.getPaused()) and + * should not be instantiated. + * + *

Example

+ * + * createjs.Ticker.addEventListener("tick", handleTick); + * function handleTick(event) { + * // Actions carried out each frame + * if (!event.paused) { + * // Actions carried out when the Ticker is not paused. + * } + * } + * + * To update a stage every tick, the {{#crossLink "Stage"}}{{/crossLink}} instance can also be used as a listener, as + * it will automatically update when it receives a tick event: + * + * createjs.Ticker.addEventListener("tick", stage); + * + * @class Ticker + * @uses EventDispatcher + * @static + **/ + function Ticker() { + throw "Ticker cannot be instantiated."; + } + // constants: /** @@ -930,13 +797,14 @@ var Ticker = function() { **/ Ticker.TIMEOUT = "timeout"; -// events: +// static events: /** * Dispatched each tick. The event will be dispatched to each listener even when the Ticker has been paused using * {{#crossLink "Ticker/setPaused"}}{{/crossLink}}. * *

Example

+ * * createjs.Ticker.addEventListener("tick", handleTick); * function handleTick(event) { * console.log("Paused:", event.paused, event.delta); @@ -953,6 +821,7 @@ var Ticker = function() { * @since 0.6.0 */ + // public static properties: /** * Deprecated in favour of {{#crossLink "Ticker/timingMode"}}{{/crossLink}}, and will be removed in a future version. If true, timingMode will @@ -993,6 +862,7 @@ var Ticker = function() { */ Ticker.maxDelta = 0; + // mix-ins: // EventDispatcher methods: Ticker.removeEventListener = null; @@ -1007,8 +877,8 @@ var Ticker = function() { return Ticker._addEventListener.apply(Ticker, arguments); }; + // private static properties: - /** * @property _paused * @type {Boolean} @@ -1097,8 +967,8 @@ var Ticker = function() { **/ Ticker._raf = true; + // public static methods: - /** * Starts the tick. This is called automatically when the first listener is added. * @method init @@ -1127,6 +997,8 @@ var Ticker = function() { clearTimeout(Ticker._timerId); } Ticker.removeAllEventListeners("tick"); + Ticker._timerId = null; + Ticker._inited = false; }; /** @@ -1194,7 +1066,7 @@ var Ticker = function() { **/ Ticker.getMeasuredTickTime = function(ticks) { var ttl=0, times=Ticker._tickTimes; - if (times.length < 1) { return -1; } + if (!times || times.length < 1) { return -1; } // by default, calculate average for the past ~1 second: ticks = Math.min(times.length, ticks||(Ticker.getFPS()|0)); @@ -1213,7 +1085,7 @@ var Ticker = function() { **/ Ticker.getMeasuredFPS = function(ticks) { var times = Ticker._times; - if (times.length < 2) { return -1; } + if (!times || times.length < 2) { return -1; } // by default, calculate fps for the past ~1 second: ticks = Math.min(times.length-1, ticks||(Ticker.getFPS()|0)); @@ -1229,6 +1101,7 @@ var Ticker = function() { * callback when Ticker was paused. This is no longer the case. * *

Example

+ * * createjs.Ticker.addEventListener("tick", handleTick); * createjs.Ticker.setPaused(true); * function handleTick(event) { @@ -1251,6 +1124,7 @@ var Ticker = function() { * callback when Ticker was paused. This is no longer the case. * *

Example

+ * * createjs.Ticker.addEventListener("tick", handleTick); * createjs.Ticker.setPaused(true); * function handleTick(event) { @@ -1266,26 +1140,27 @@ var Ticker = function() { }; /** - * Returns the number of milliseconds that have elapsed since Ticker was initialized. For example, you could use + * Returns the number of milliseconds that have elapsed since Ticker was initialized via {{#crossLink "Ticker/init"}}. + * Returns -1 if Ticker has not been initialized. For example, you could use * this in a time synchronized animation to determine the exact amount of time that has elapsed. * @method getTime * @static * @param {Boolean} [runTime=false] If true only time elapsed while Ticker was not paused will be returned. * If false, the value returned will be total time elapsed since the first tick event listener was added. - * @return {Number} Number of milliseconds that have elapsed since Ticker was initialized. + * @return {Number} Number of milliseconds that have elapsed since Ticker was initialized or -1. **/ Ticker.getTime = function(runTime) { - return Ticker._getTime() - Ticker._startTime - (runTime ? Ticker._pausedTime : 0); + return Ticker._startTime ? Ticker._getTime() - Ticker._startTime - (runTime ? Ticker._pausedTime : 0) : -1; }; /** * Similar to getTime(), but returns the time included with the current (or most recent) tick event object. * @method getEventTime * @param runTime {Boolean} [runTime=false] If true, the runTime property will be returned instead of time. - * @returns {number} The time or runTime property from the most recent tick event. + * @returns {number} The time or runTime property from the most recent tick event or -1. */ Ticker.getEventTime = function(runTime) { - return (Ticker._lastTime || Ticker._startTime) - (runTime ? Ticker._pausedTime : 0); + return Ticker._startTime ? (Ticker._lastTime || Ticker._startTime) - (runTime ? Ticker._pausedTime : 0) : -1; }; /** @@ -1302,6 +1177,7 @@ var Ticker = function() { return Ticker._ticks - (pauseable ?Ticker._pausedTicks : 0); }; + // private static methods: /** * @method _handleSynch @@ -1309,12 +1185,11 @@ var Ticker = function() { * @protected **/ Ticker._handleSynch = function() { - var time = Ticker._getTime() - Ticker._startTime; Ticker._timerId = null; Ticker._setupTick(); // run if enough time has elapsed, with a little bit of flexibility to be early: - if (time - Ticker._lastTime >= (Ticker._interval-1)*0.97) { + if (Ticker._getTime() - Ticker._lastTime >= (Ticker._interval-1)*0.97) { Ticker._tick(); } }; @@ -1368,7 +1243,8 @@ var Ticker = function() { * @protected **/ Ticker._tick = function() { - var time = Ticker._getTime()-Ticker._startTime; + var time = Ticker._getTime(); + var adjTime = time-Ticker._startTime; var elapsedTime = time-Ticker._lastTime; var paused = Ticker._paused; @@ -1384,15 +1260,15 @@ var Ticker = function() { var maxDelta = Ticker.maxDelta; event.delta = (maxDelta && elapsedTime > maxDelta) ? maxDelta : elapsedTime; event.paused = paused; - event.time = time; - event.runTime = time-Ticker._pausedTime; + event.time = adjTime; + event.runTime = adjTime-Ticker._pausedTime; Ticker.dispatchEvent(event); } Ticker._tickTimes.unshift(Ticker._getTime()-time); while (Ticker._tickTimes.length > 100) { Ticker._tickTimes.pop(); } - Ticker._times.unshift(time); + Ticker._times.unshift(adjTime); while (Ticker._times.length > 100) { Ticker._times.pop(); } }; @@ -1406,165 +1282,147 @@ var Ticker = function() { return (now&&now.call(performance))||(new Date().getTime()); }; -createjs.Ticker = Ticker; + + createjs.Ticker = Ticker; }()); -/* -* MouseEvent -* Visit http://createjs.com/ for documentation, updates and examples. -* -* Copyright (c) 2010 gskinner.com, inc. -* -* Permission is hereby granted, free of charge, to any person -* obtaining a copy of this software and associated documentation -* files (the "Software"), to deal in the Software without -* restriction, including without limitation the rights to use, -* copy, modify, merge, publish, distribute, sublicense, and/or sell -* copies of the Software, and to permit persons to whom the -* Software is furnished to do so, subject to the following -* conditions: -* -* The above copyright notice and this permission notice shall be -* included in all copies or substantial portions of the Software. -* -* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES -* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT -* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR -* OTHER DEALINGS IN THE SOFTWARE. -*/ -/** - * @module EaselJS - */ +//############################################################################## +// UID.js +//############################################################################## -// namespace: this.createjs = this.createjs||{}; (function() { "use strict"; -/** - * Passed as the parameter to all mouse/pointer/touch related events. For a listing of mouse events and their properties, - * see the {{#crossLink "DisplayObject"}}{{/crossLink}} and {{#crossLink "Stage"}}{{/crossLink}} event listings. - * @class MouseEvent - * @param {String} type The event type. - * @param {Boolean} bubbles Indicates whether the event will bubble through the display list. - * @param {Boolean} cancelable Indicates whether the default behaviour of this event can be cancelled. - * @param {Number} stageX The normalized x position relative to the stage. - * @param {Number} stageY The normalized y position relative to the stage. - * @param {MouseEvent} nativeEvent The native DOM event related to this mouse event. - * @param {Number} pointerID The unique id for the pointer. - * @param {Boolean} primary Indicates whether this is the primary pointer in a multitouch environment. - * @param {Number} rawX The raw x position relative to the stage. - * @param {Number} rawY The raw y position relative to the stage. - * @extends Event - * @constructor - **/ -var MouseEvent = function(type, bubbles, cancelable, stageX, stageY, nativeEvent, pointerID, primary, rawX, rawY) { - this.initialize(type, bubbles, cancelable, stageX, stageY, nativeEvent, pointerID, primary, rawX, rawY); -}; -var p = MouseEvent.prototype = new createjs.Event(); -// events: - // TODO: deprecated. +// constructor: /** - * REMOVED. For MouseEvent objects of type "mousedown", mousemove events will be dispatched from the event object until the - * user releases the mouse anywhere. This enables you to listen to mouse move interactions for the duration of a - * press, which can be very useful for operations such as drag and drop. - * - * See the {{#crossLink "MouseEvent"}}{{/crossLink}} class description for more information on mouse events. - * @event mousemove - * @since 0.6.0 - * @deprecated In favour of the DisplayObject "pressmove" event. - */ - - /** - * REMOVED. For MouseEvent objects of type "mousedown", a mouseup event will be dispatched from the event object when the - * user releases the mouse anywhere. This enables you to listen for a corresponding mouse up from a specific press, - * which can be very useful for operations such as drag and drop. - * - * See the {{#crossLink "MouseEvent"}}{{/crossLink}} class description for more information on mouse events. - * @event mouseup - * @since 0.6.0 - * @deprecated In favour of the DisplayObject "pressup" event. - */ - -// public properties: - - /** - * The normalized x position on the stage. This will always be within the range 0 to stage width. - * @property stageX - * @type Number - */ - p.stageX = 0; - - /** - * The normalized y position on the stage. This will always be within the range 0 to stage height. - * @property stageY - * @type Number + * Global utility for generating sequential unique ID numbers. The UID class uses a static interface (ex. UID.get()) + * and should not be instantiated. + * @class UID + * @static **/ - p.stageY = 0; + function UID() { + throw "UID cannot be instantiated"; + } + +// private static properties: /** - * The raw x position relative to the stage. Normally this will be the same as the stageX value, unless - * stage.mouseMoveOutside is true and the pointer is outside of the stage bounds. - * @property rawX + * @property _nextID * @type Number - */ - p.rawX = 0; - - /** - * The raw y position relative to the stage. Normally this will be the same as the stageY value, unless - * stage.mouseMoveOutside is true and the pointer is outside of the stage bounds. - * @property rawY - * @type Number - */ - p.rawY = 0; - - /** - * The native MouseEvent generated by the browser. The properties and API for this - * event may differ between browsers. This property will be null if the - * EaselJS property was not directly generated from a native MouseEvent. - * @property nativeEvent - * @type HtmlMouseEvent - * @default null + * @protected **/ - p.nativeEvent = null; + UID._nextID = 0; - // TODO: deprecated: - /** - * REMOVED. Use the {{#crossLink "DisplayObject"}}{{/crossLink}} {{#crossLink "DisplayObject/pressmove:event"}}{{/crossLink}} - * event. - * @property onMouseMove - * @type Function - * @deprecated Use the DisplayObject "pressmove" event. - */ - /** - * REMOVED. Use the {{#crossLink "DisplayObject"}}{{/crossLink}} {{#crossLink "DisplayObject/pressup:event"}}{{/crossLink}} - * event. - * @property onMouseUp - * @type Function - * @deprecated Use the DisplayObject "pressup" event. - */ +// public static methods: /** - * The unique id for the pointer (touch point or cursor). This will be either -1 for the mouse, or the system - * supplied id value. - * @property pointerID - * @type {Number} - */ - p.pointerID = 0; + * Returns the next unique id. + * @method get + * @return {Number} The next unique id + * @static + **/ + UID.get = function() { + return UID._nextID++; + }; + + createjs.UID = UID; +}()); + +//############################################################################## +// MouseEvent.js +//############################################################################## + +this.createjs = this.createjs||{}; + +(function() { + "use strict"; + + +// constructor: /** - * Indicates whether this is the primary pointer in a multitouch environment. This will always be true for the mouse. - * For touch pointers, the first pointer in the current stack will be considered the primary pointer. - * @property primary - * @type {Boolean} - */ - p.primary = false; + * Passed as the parameter to all mouse/pointer/touch related events. For a listing of mouse events and their properties, + * see the {{#crossLink "DisplayObject"}}{{/crossLink}} and {{#crossLink "Stage"}}{{/crossLink}} event listings. + * @class MouseEvent + * @param {String} type The event type. + * @param {Boolean} bubbles Indicates whether the event will bubble through the display list. + * @param {Boolean} cancelable Indicates whether the default behaviour of this event can be cancelled. + * @param {Number} stageX The normalized x position relative to the stage. + * @param {Number} stageY The normalized y position relative to the stage. + * @param {MouseEvent} nativeEvent The native DOM event related to this mouse event. + * @param {Number} pointerID The unique id for the pointer. + * @param {Boolean} primary Indicates whether this is the primary pointer in a multitouch environment. + * @param {Number} rawX The raw x position relative to the stage. + * @param {Number} rawY The raw y position relative to the stage. + * @extends Event + * @constructor + **/ + function MouseEvent(type, bubbles, cancelable, stageX, stageY, nativeEvent, pointerID, primary, rawX, rawY) { + this.Event_constructor(type, bubbles, cancelable); + + + // public properties: + /** + * The normalized x position on the stage. This will always be within the range 0 to stage width. + * @property stageX + * @type Number + */ + this.stageX = stageX; + + /** + * The normalized y position on the stage. This will always be within the range 0 to stage height. + * @property stageY + * @type Number + **/ + this.stageY = stageY; + + /** + * The raw x position relative to the stage. Normally this will be the same as the stageX value, unless + * stage.mouseMoveOutside is true and the pointer is outside of the stage bounds. + * @property rawX + * @type Number + */ + this.rawX = (rawX==null)?stageX:rawX; + + /** + * The raw y position relative to the stage. Normally this will be the same as the stageY value, unless + * stage.mouseMoveOutside is true and the pointer is outside of the stage bounds. + * @property rawY + * @type Number + */ + this.rawY = (rawY==null)?stageY:rawY; + + /** + * The native MouseEvent generated by the browser. The properties and API for this + * event may differ between browsers. This property will be null if the + * EaselJS property was not directly generated from a native MouseEvent. + * @property nativeEvent + * @type HtmlMouseEvent + * @default null + **/ + this.nativeEvent = nativeEvent; + + /** + * The unique id for the pointer (touch point or cursor). This will be either -1 for the mouse, or the system + * supplied id value. + * @property pointerID + * @type {Number} + */ + this.pointerID = pointerID; + + /** + * Indicates whether this is the primary pointer in a multitouch environment. This will always be true for the mouse. + * For touch pointers, the first pointer in the current stack will be considered the primary pointer. + * @property primary + * @type {Boolean} + */ + this.primary = !!primary; + } + var p = createjs.extend(MouseEvent, createjs.Event); + // getter / setters: /** @@ -1594,39 +1452,6 @@ var p = MouseEvent.prototype = new createjs.Event(); }); } catch (e) {} // TODO: use Log -// constructor: - /** - * @property Event_initialize - * @private - * @type Function - **/ - p.Event_initialize = p.initialize; - - /** - * Initialization method. - * @method initialize - * @param {String} type The event type. - * @param {Boolean} bubbles Indicates whether the event will bubble through the display list. - * @param {Boolean} cancelable Indicates whether the default behaviour of this event can be cancelled. - * @param {Number} stageX The normalized x position relative to the stage. - * @param {Number} stageY The normalized y position relative to the stage. - * @param {MouseEvent} nativeEvent The native DOM event related to this mouse event. - * @param {Number} pointerID The unique id for the pointer. - * @param {Boolean} primary Indicates whether this is the primary pointer in a multitouch environment. - * @param {Number} rawX The raw x position relative to the stage. - * @param {Number} rawY The raw y position relative to the stage. - * @protected - **/ - p.initialize = function(type, bubbles, cancelable, stageX, stageY, nativeEvent, pointerID, primary, rawX, rawY) { - this.Event_initialize(type, bubbles, cancelable); - this.stageX = stageX; - this.stageY = stageY; - this.nativeEvent = nativeEvent; - this.pointerID = pointerID; - this.primary = primary; - this.rawX = (rawX==null)?stageX:rawX; - this.rawY = (rawY==null)?stageY:rawY; - }; // public methods: /** @@ -1635,7 +1460,7 @@ var p = MouseEvent.prototype = new createjs.Event(); * @return {MouseEvent} a clone of the MouseEvent instance. **/ p.clone = function() { - return new MouseEvent(this.type, this.bubbles, this.cancelable, this.stageX, this.stageY, this.target, this.nativeEvent, this.pointerID, this.primary, this.rawX, this.rawY); + return new MouseEvent(this.type, this.bubbles, this.cancelable, this.stageX, this.stageY, this.nativeEvent, this.pointerID, this.primary, this.rawX, this.rawY); }; /** @@ -1647,73 +1472,77 @@ var p = MouseEvent.prototype = new createjs.Event(); return "[MouseEvent (type="+this.type+" stageX="+this.stageX+" stageY="+this.stageY+")]"; }; -createjs.MouseEvent = MouseEvent; + + createjs.MouseEvent = createjs.promote(MouseEvent, "Event"); }()); -/* -* Matrix2D -* Visit http://createjs.com/ for documentation, updates and examples. -* -* Copyright (c) 2010 gskinner.com, inc. -* -* Permission is hereby granted, free of charge, to any person -* obtaining a copy of this software and associated documentation -* files (the "Software"), to deal in the Software without -* restriction, including without limitation the rights to use, -* copy, modify, merge, publish, distribute, sublicense, and/or sell -* copies of the Software, and to permit persons to whom the -* Software is furnished to do so, subject to the following -* conditions: -* -* The above copyright notice and this permission notice shall be -* included in all copies or substantial portions of the Software. -* -* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES -* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT -* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR -* OTHER DEALINGS IN THE SOFTWARE. -*/ -/** - * @module EaselJS - */ +//############################################################################## +// Matrix2D.js +//############################################################################## -// namespace: this.createjs = this.createjs||{}; (function() { "use strict"; -/** - * Represents an affine transformation matrix, and provides tools for constructing and concatenating matrixes. - * @class Matrix2D - * @param {Number} [a=1] Specifies the a property for the new matrix. - * @param {Number} [b=0] Specifies the b property for the new matrix. - * @param {Number} [c=0] Specifies the c property for the new matrix. - * @param {Number} [d=1] Specifies the d property for the new matrix. - * @param {Number} [tx=0] Specifies the tx property for the new matrix. - * @param {Number} [ty=0] Specifies the ty property for the new matrix. - * @constructor - **/ -var Matrix2D = function(a, b, c, d, tx, ty) { - this.initialize(a, b, c, d, tx, ty); -}; -var p = Matrix2D.prototype; - -// static public properties: +// constructor: /** - * An identity matrix, representing a null transformation. - * @property identity - * @static - * @type Matrix2D - * @readonly + * Represents an affine transformation matrix, and provides tools for constructing and concatenating matrixes. + * @class Matrix2D + * @param {Number} [a=1] Specifies the a property for the new matrix. + * @param {Number} [b=0] Specifies the b property for the new matrix. + * @param {Number} [c=0] Specifies the c property for the new matrix. + * @param {Number} [d=1] Specifies the d property for the new matrix. + * @param {Number} [tx=0] Specifies the tx property for the new matrix. + * @param {Number} [ty=0] Specifies the ty property for the new matrix. + * @constructor **/ - Matrix2D.identity = null; // set at bottom of class definition. + function Matrix2D(a, b, c, d, tx, ty) { + this.setValues(a,b,c,d,tx,ty); + + // public properties: + // assigned in the setValues method. + /** + * Position (0, 0) in a 3x3 affine transformation matrix. + * @property a + * @type Number + **/ + + /** + * Position (0, 1) in a 3x3 affine transformation matrix. + * @property b + * @type Number + **/ + + /** + * Position (1, 0) in a 3x3 affine transformation matrix. + * @property c + * @type Number + **/ + + /** + * Position (1, 1) in a 3x3 affine transformation matrix. + * @property d + * @type Number + **/ + + /** + * Position (2, 0) in a 3x3 affine transformation matrix. + * @property tx + * @type Number + **/ + + /** + * Position (2, 1) in a 3x3 affine transformation matrix. + * @property ty + * @type Number + **/ + } + var p = Matrix2D.prototype; + +// constants: /** * Multiplier for converting degrees to radians. Used internally by Matrix2D. * @property DEG_TO_RAD @@ -1725,87 +1554,21 @@ var p = Matrix2D.prototype; Matrix2D.DEG_TO_RAD = Math.PI/180; -// public properties: +// static public properties: /** - * Position (0, 0) in a 3x3 affine transformation matrix. - * @property a - * @type Number + * An identity matrix, representing a null transformation. + * @property identity + * @static + * @type Matrix2D + * @readonly **/ - p.a = 1; - - /** - * Position (0, 1) in a 3x3 affine transformation matrix. - * @property b - * @type Number - **/ - p.b = 0; - - /** - * Position (1, 0) in a 3x3 affine transformation matrix. - * @property c - * @type Number - **/ - p.c = 0; - - /** - * Position (1, 1) in a 3x3 affine transformation matrix. - * @property d - * @type Number - **/ - p.d = 1; - - /** - * Position (2, 0) in a 3x3 affine transformation matrix. - * @property tx - * @type Number - **/ - p.tx = 0; - - /** - * Position (2, 1) in a 3x3 affine transformation matrix. - * @property ty - * @type Number - **/ - p.ty = 0; - - /** - * Property representing the alpha that will be applied to a display object. This is not part of matrix - * operations, but is used for operations like getConcatenatedMatrix to provide concatenated alpha values. - * @property alpha - * @type Number - **/ - p.alpha = 1; - - /** - * Property representing the shadow that will be applied to a display object. This is not part of matrix - * operations, but is used for operations like getConcatenatedMatrix to provide concatenated shadow values. - * @property shadow - * @type Shadow - **/ - p.shadow = null; - - /** - * Property representing the compositeOperation that will be applied to a display object. This is not part of - * matrix operations, but is used for operations like getConcatenatedMatrix to provide concatenated - * compositeOperation values. You can find a list of valid composite operations at: - * https://developer.mozilla.org/en/Canvas_tutorial/Compositing - * @property compositeOperation - * @type String - **/ - p.compositeOperation = null; + Matrix2D.identity = null; // set at bottom of class definition. - /** - * Property representing the value for visible that will be applied to a display object. This is not part of matrix - * operations, but is used for operations like getConcatenatedMatrix to provide concatenated visible values. - * @property visible - * @type Boolean - **/ - p.visible = true; -// constructor: +// public methods: /** - * Initialization method. Can also be used to reinitialize the instance. - * @method initialize + * Sets the specified values on this instance. + * @method setValues * @param {Number} [a=1] Specifies the a property for the new matrix. * @param {Number} [b=0] Specifies the b property for the new matrix. * @param {Number} [c=0] Specifies the c property for the new matrix. @@ -1814,7 +1577,8 @@ var p = Matrix2D.prototype; * @param {Number} [ty=0] Specifies the ty property for the new matrix. * @return {Matrix2D} This instance. Useful for chaining method calls. */ - p.initialize = function(a, b, c, d, tx, ty) { + p.setValues = function(a, b, c, d, tx, ty) { + // don't forget to update docs in the constructor if these change: this.a = (a == null) ? 1 : a; this.b = b || 0; this.c = c || 0; @@ -1824,10 +1588,10 @@ var p = Matrix2D.prototype; return this; }; -// public methods: /** - * Concatenates the specified matrix properties with this matrix. All parameters are required. - * @method prepend + * Appends the specified matrix properties to this matrix. All parameters are required. + * This is the equivalent of multiplying `(this matrix) * (specified matrix)`. + * @method append * @param {Number} a * @param {Number} b * @param {Number} c @@ -1836,7 +1600,7 @@ var p = Matrix2D.prototype; * @param {Number} ty * @return {Matrix2D} This matrix. Useful for chaining method calls. **/ - p.prepend = function(a, b, c, d, tx, ty) { + p.append = function(a, b, c, d, tx, ty) { var tx1 = this.tx; if (a != 1 || b != 0 || c != 0 || d != 1) { var a1 = this.a; @@ -1852,8 +1616,10 @@ var p = Matrix2D.prototype; }; /** - * Appends the specified matrix properties with this matrix. All parameters are required. - * @method append + * Prepends the specified matrix properties to this matrix. + * This is the equivalent of multiplying `(specified matrix) * (this matrix)`. + * All parameters are required. + * @method prepend * @param {Number} a * @param {Number} b * @param {Number} c @@ -1862,7 +1628,7 @@ var p = Matrix2D.prototype; * @param {Number} ty * @return {Matrix2D} This matrix. Useful for chaining method calls. **/ - p.append = function(a, b, c, d, tx, ty) { + p.prepend = function(a, b, c, d, tx, ty) { var a1 = this.a; var b1 = this.b; var c1 = this.c; @@ -1878,75 +1644,40 @@ var p = Matrix2D.prototype; }; /** - * Prepends the specified matrix with this matrix. - * @method prependMatrix - * @param {Matrix2D} matrix - * @return {Matrix2D} This matrix. Useful for chaining method calls. - **/ - p.prependMatrix = function(matrix) { - this.prepend(matrix.a, matrix.b, matrix.c, matrix.d, matrix.tx, matrix.ty); - this.prependProperties(matrix.alpha, matrix.shadow, matrix.compositeOperation, matrix.visible); - return this; - }; - - /** - * Appends the specified matrix with this matrix. + * Appends the specified matrix to this matrix. + * This is the equivalent of multiplying `(this matrix) * (specified matrix)`. + * For example, you could calculate the combined transformation for a child object using: + * var o = myDisplayObject; + * var mtx = o.getMatrix(); + * while (o = o.parent) { + * // append each parent's transformation in turn: + * o.appendMatrix(o.getMatrix()); + * } * @method appendMatrix * @param {Matrix2D} matrix * @return {Matrix2D} This matrix. Useful for chaining method calls. **/ p.appendMatrix = function(matrix) { - this.append(matrix.a, matrix.b, matrix.c, matrix.d, matrix.tx, matrix.ty); - this.appendProperties(matrix.alpha, matrix.shadow, matrix.compositeOperation, matrix.visible); - return this; + return this.append(matrix.a, matrix.b, matrix.c, matrix.d, matrix.tx, matrix.ty); }; /** - * Generates matrix properties from the specified display object transform properties, and prepends them with this matrix. - * For example, you can use this to generate a matrix from a display object: var mtx = new Matrix2D(); - * mtx.prependTransform(o.x, o.y, o.scaleX, o.scaleY, o.rotation); - * @method prependTransform - * @param {Number} x - * @param {Number} y - * @param {Number} scaleX - * @param {Number} scaleY - * @param {Number} rotation - * @param {Number} skewX - * @param {Number} skewY - * @param {Number} regX Optional. - * @param {Number} regY Optional. + * Prepends the specified matrix to this matrix. + * This is the equivalent of multiplying `(specified matrix) * (this matrix)`. + * @method prependMatrix + * @param {Matrix2D} matrix * @return {Matrix2D} This matrix. Useful for chaining method calls. **/ - p.prependTransform = function(x, y, scaleX, scaleY, rotation, skewX, skewY, regX, regY) { - if (rotation%360) { - var r = rotation*Matrix2D.DEG_TO_RAD; - var cos = Math.cos(r); - var sin = Math.sin(r); - } else { - cos = 1; - sin = 0; - } - - if (regX || regY) { - // append the registration offset: - this.tx -= regX; this.ty -= regY; - } - if (skewX || skewY) { - // TODO: can this be combined into a single prepend operation? - skewX *= Matrix2D.DEG_TO_RAD; - skewY *= Matrix2D.DEG_TO_RAD; - this.prepend(cos*scaleX, sin*scaleX, -sin*scaleY, cos*scaleY, 0, 0); - this.prepend(Math.cos(skewY), Math.sin(skewY), -Math.sin(skewX), Math.cos(skewX), x, y); - } else { - this.prepend(cos*scaleX, sin*scaleX, -sin*scaleY, cos*scaleY, x, y); - } - return this; + p.prependMatrix = function(matrix) { + return this.prepend(matrix.a, matrix.b, matrix.c, matrix.d, matrix.tx, matrix.ty); }; /** - * Generates matrix properties from the specified display object transform properties, and appends them with this matrix. - * For example, you can use this to generate a matrix from a display object: var mtx = new Matrix2D(); - * mtx.appendTransform(o.x, o.y, o.scaleX, o.scaleY, o.rotation); + * Generates matrix properties from the specified display object transform properties, and appends them to this matrix. + * For example, you can use this to generate a matrix from a display object: + * + * var mtx = new Matrix2D(); + * mtx.appendTransform(o.x, o.y, o.scaleX, o.scaleY, o.rotation); * @method appendTransform * @param {Number} x * @param {Number} y @@ -1969,14 +1700,58 @@ var p = Matrix2D.prototype; sin = 0; } + if (regX || regY) { + // append the registration offset: + this.tx -= regX; this.ty -= regY; + } + if (skewX || skewY) { + // TODO: can this be combined into a single prepend operation? + skewX *= Matrix2D.DEG_TO_RAD; + skewY *= Matrix2D.DEG_TO_RAD; + this.append(cos*scaleX, sin*scaleX, -sin*scaleY, cos*scaleY, 0, 0); + this.append(Math.cos(skewY), Math.sin(skewY), -Math.sin(skewX), Math.cos(skewX), x, y); + } else { + this.append(cos*scaleX, sin*scaleX, -sin*scaleY, cos*scaleY, x, y); + } + return this; + }; + + /** + * Generates matrix properties from the specified display object transform properties, and prepends them to this matrix. + * For example, you can use this to generate a matrix from a display object: + * + * var mtx = new Matrix2D(); + * mtx.prependTransform(o.x, o.y, o.scaleX, o.scaleY, o.rotation); + * @method prependTransform + * @param {Number} x + * @param {Number} y + * @param {Number} scaleX + * @param {Number} scaleY + * @param {Number} rotation + * @param {Number} skewX + * @param {Number} skewY + * @param {Number} regX Optional. + * @param {Number} regY Optional. + * @return {Matrix2D} This matrix. Useful for chaining method calls. + **/ + p.prependTransform = function(x, y, scaleX, scaleY, rotation, skewX, skewY, regX, regY) { + if (rotation%360) { + var r = rotation*Matrix2D.DEG_TO_RAD; + var cos = Math.cos(r); + var sin = Math.sin(r); + } else { + cos = 1; + sin = 0; + } + if (skewX || skewY) { // TODO: can this be combined into a single append? skewX *= Matrix2D.DEG_TO_RAD; skewY *= Matrix2D.DEG_TO_RAD; - this.append(Math.cos(skewY), Math.sin(skewY), -Math.sin(skewX), Math.cos(skewX), x, y); - this.append(cos*scaleX, sin*scaleX, -sin*scaleY, cos*scaleY, 0, 0); + this.prepend(Math.cos(skewY), Math.sin(skewY), -Math.sin(skewX), Math.cos(skewX), x, y); + this.prepend(cos*scaleX, sin*scaleX, -sin*scaleY, cos*scaleY, 0, 0); } else { - this.append(cos*scaleX, sin*scaleX, -sin*scaleY, cos*scaleY, x, y); + this.prepend(cos*scaleX, sin*scaleX, -sin*scaleY, cos*scaleY, x, y); } if (regX || regY) { @@ -1990,7 +1765,7 @@ var p = Matrix2D.prototype; /** * Applies a rotation transformation to the matrix. * @method rotate - * @param {Number} angle The angle in radians. To use degrees, multiply by Math.PI/180. + * @param {Number} angle The angle in radians. To use degrees, multiply by `Math.PI/180`. * @return {Matrix2D} This matrix. Useful for chaining method calls. **/ p.rotate = function(angle) { @@ -2020,7 +1795,7 @@ var p = Matrix2D.prototype; p.skew = function(skewX, skewY) { skewX = skewX*Matrix2D.DEG_TO_RAD; skewY = skewY*Matrix2D.DEG_TO_RAD; - this.append(Math.cos(skewY), Math.sin(skewY), -Math.sin(skewX), Math.cos(skewX), 0, 0); + this.prepend(Math.cos(skewY), Math.sin(skewY), -Math.sin(skewX), Math.cos(skewX), 0, 0); return this; }; @@ -2060,10 +1835,8 @@ var p = Matrix2D.prototype; * @return {Matrix2D} This matrix. Useful for chaining method calls. **/ p.identity = function() { - this.alpha = this.a = this.d = 1; + this.a = this.d = 1; this.b = this.c = this.tx = this.ty = 0; - this.shadow = this.compositeOperation = null; - this.visible = true; return this; }; @@ -2095,7 +1868,17 @@ var p = Matrix2D.prototype; * @return {Boolean} **/ p.isIdentity = function() { - return this.tx == 0 && this.ty == 0 && this.a == 1 && this.b == 0 && this.c == 0 && this.d == 1; + return this.tx === 0 && this.ty === 0 && this.a === 1 && this.b === 0 && this.c === 0 && this.d === 1; + }; + + /** + * Returns true if this matrix is equal to the specified matrix (all property values are equal). + * @method equals + * @param {Matrix2D} matrix The matrix to compare. + * @return {Boolean} + **/ + p.equals = function(matrix) { + return this.tx === matrix.tx && this.ty === matrix.ty && this.a === matrix.a && this.b === matrix.b && this.c === matrix.c && this.d === matrix.d; }; /** @@ -2114,16 +1897,15 @@ var p = Matrix2D.prototype; }; /** - * Decomposes the matrix into transform properties (x, y, scaleX, scaleY, and rotation). Note that this these values + * Decomposes the matrix into transform properties (x, y, scaleX, scaleY, and rotation). Note that these values * may not match the transform properties you used to generate the matrix, though they will produce the same visual * results. * @method decompose * @param {Object} target The object to apply the transform properties to. If null, then a new object will be returned. - * @return {Matrix2D} This matrix. Useful for chaining method calls. + * @return {Object} The target, or a new generic object with the transform properties applied. */ p.decompose = function(target) { - // TODO: it would be nice to be able to solve for whether the matrix can be decomposed into only scale/rotation - // even when scale is negative + // TODO: it would be nice to be able to solve for whether the matrix can be decomposed into only scale/rotation even when scale is negative if (target == null) { target = {}; } target.x = this.tx; target.y = this.ty; @@ -2133,7 +1915,8 @@ var p = Matrix2D.prototype; var skewX = Math.atan2(-this.c, this.d); var skewY = Math.atan2(this.b, this.a); - if (skewX == skewY) { + var delta = Math.abs(1-skewX/skewY); + if (delta < 0.00001) { // effectively identical, can use rotation: target.rotation = skewY/Matrix2D.DEG_TO_RAD; if (this.a < 0 && this.d >= 0) { target.rotation += (target.rotation <= 0) ? 180 : -180; @@ -2145,30 +1928,6 @@ var p = Matrix2D.prototype; } return target; }; - - /** - * Reinitializes all matrix properties to those specified. - * @method reinitialize - * @param {Number} [a=1] Specifies the a property for the new matrix. - * @param {Number} [b=0] Specifies the b property for the new matrix. - * @param {Number} [c=0] Specifies the c property for the new matrix. - * @param {Number} [d=1] Specifies the d property for the new matrix. - * @param {Number} [tx=0] Specifies the tx property for the new matrix. - * @param {Number} [ty=0] Specifies the ty property for the new matrix. - * @param {Number} [alpha=1] desired alpha value - * @param {Shadow} [shadow=null] desired shadow value - * @param {String} [compositeOperation=null] desired composite operation value - * @param {Boolean} [visible=true] desired visible value - * @return {Matrix2D} This matrix. Useful for chaining method calls. - */ - p.reinitialize = function(a, b, c, d, tx, ty, alpha, shadow, compositeOperation, visible) { - this.initialize(a,b,c,d,tx,ty); - this.alpha = alpha == null ? 1 : alpha; - this.shadow = shadow; - this.compositeOperation = compositeOperation; - this.visible = visible == null ? true : visible; - return this; - }; /** * Copies all properties from the specified matrix to this matrix. @@ -2177,41 +1936,7 @@ var p = Matrix2D.prototype; * @return {Matrix2D} This matrix. Useful for chaining method calls. */ p.copy = function(matrix) { - return this.reinitialize(matrix.a, matrix.b, matrix.c, matrix.d, matrix.tx, matrix.ty, matrix.alpha, matrix.shadow, matrix.compositeOperation, matrix.visible); - }; - - /** - * Appends the specified visual properties to the current matrix. - * @method appendProperties - * @param {Number} alpha desired alpha value - * @param {Shadow} shadow desired shadow value - * @param {String} compositeOperation desired composite operation value - * @param {Boolean} visible desired visible value - * @return {Matrix2D} This matrix. Useful for chaining method calls. - */ - p.appendProperties = function(alpha, shadow, compositeOperation, visible) { - this.alpha *= alpha; - this.shadow = shadow || this.shadow; - this.compositeOperation = compositeOperation || this.compositeOperation; - this.visible = this.visible && visible; - return this; - }; - - /** - * Prepends the specified visual properties to the current matrix. - * @method prependProperties - * @param {Number} alpha desired alpha value - * @param {Shadow} shadow desired shadow value - * @param {String} compositeOperation desired composite operation value - * @param {Boolean} visible desired visible value - * @return {Matrix2D} This matrix. Useful for chaining method calls. - */ - p.prependProperties = function(alpha, shadow, compositeOperation, visible) { - this.alpha *= alpha; - this.shadow = this.shadow || shadow; - this.compositeOperation = this.compositeOperation || compositeOperation; - this.visible = this.visible && visible; - return this; + return this.setValues(matrix.a, matrix.b, matrix.c, matrix.d, matrix.tx, matrix.ty); }; /** @@ -2220,7 +1945,7 @@ var p = Matrix2D.prototype; * @return {Matrix2D} a clone of the Matrix2D instance. **/ p.clone = function() { - return (new Matrix2D()).copy(this); + return new Matrix2D(this.a, this.b, this.c, this.d, this.tx, this.ty); }; /** @@ -2235,93 +1960,220 @@ var p = Matrix2D.prototype; // this has to be populated after the class is defined: Matrix2D.identity = new Matrix2D(); -createjs.Matrix2D = Matrix2D; + + createjs.Matrix2D = Matrix2D; }()); -/* -* Point -* Visit http://createjs.com/ for documentation, updates and examples. -* -* Copyright (c) 2010 gskinner.com, inc. -* -* Permission is hereby granted, free of charge, to any person -* obtaining a copy of this software and associated documentation -* files (the "Software"), to deal in the Software without -* restriction, including without limitation the rights to use, -* copy, modify, merge, publish, distribute, sublicense, and/or sell -* copies of the Software, and to permit persons to whom the -* Software is furnished to do so, subject to the following -* conditions: -* -* The above copyright notice and this permission notice shall be -* included in all copies or substantial portions of the Software. -* -* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES -* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT -* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR -* OTHER DEALINGS IN THE SOFTWARE. -*/ -/** - * @module EaselJS - */ +//############################################################################## +// DisplayProps.js +//############################################################################## -// namespace: this.createjs = this.createjs||{}; (function() { "use strict"; -/** - * Represents a point on a 2 dimensional x / y coordinate system. - * - *

Example

- * var point = new Point(0, 100); - * - * @class Point - * @param {Number} [x=0] X position. - * @param {Number} [y=0] Y position. - * @constructor - **/ -var Point = function(x, y) { - this.initialize(x, y); -}; -var p = Point.prototype; - -// public properties: - /** - * X position. - * @property x - * @type Number + * Used for calculating and encapsulating display related properties. + * @class DisplayProps + * @param {Number} [visible=true] Visible value. + * @param {Number} [alpha=0] Alpha value. + * @param {Number} [shadow=null] A Shadow instance or null. + * @param {Number} [compositeOperation=null] A compositeOperation value or null. + * @param {Number} [matrix] A transformation matrix. Defaults to a new identity matrix. + * @constructor **/ - p.x = 0; + function DisplayProps(visible, alpha, shadow, compositeOperation, matrix) { + this.setValues(visible, alpha, shadow, compositeOperation, matrix); + + // public properties: + // assigned in the setValues method. + /** + * Property representing the alpha that will be applied to a display object. + * @property alpha + * @type Number + **/ + + /** + * Property representing the shadow that will be applied to a display object. + * @property shadow + * @type Shadow + **/ + + /** + * Property representing the compositeOperation that will be applied to a display object. + * You can find a list of valid composite operations at: + * https://developer.mozilla.org/en/Canvas_tutorial/Compositing + * @property compositeOperation + * @type String + **/ + + /** + * Property representing the value for visible that will be applied to a display object. + * @property visible + * @type Boolean + **/ + + /** + * The transformation matrix that will be applied to a display object. + * @property matrix + * @type Matrix2D + **/ + } + var p = DisplayProps.prototype; +// initialization: /** - * Y position. - * @property y - * @type Number + * Reinitializes the instance with the specified values. + * @method setValues + * @param {Number} [visible=true] Visible value. + * @param {Number} [alpha=1] Alpha value. + * @param {Number} [shadow=null] A Shadow instance or null. + * @param {Number} [compositeOperation=null] A compositeOperation value or null. + * @param {Number} [matrix] A transformation matrix. Defaults to an identity matrix. + * @return {DisplayProps} This instance. Useful for chaining method calls. + */ + p.setValues = function (visible, alpha, shadow, compositeOperation, matrix) { + this.visible = visible == null ? true : !!visible; + this.alpha = alpha == null ? 1 : alpha; + this.shadow = shadow; + this.compositeOperation = shadow; + this.matrix = matrix || (this.matrix&&this.matrix.identity()) || new createjs.Matrix2D(); + return this; + }; + +// public methods: + /** + * Prepends the specified display properties. This is generally used to apply a parent's properties to a child's. + * For example, to get the combined display properties that would be applied to a child, you could use: + * var o = myDisplayObject; + * var props = new createjs.DisplayProps(); + * do { + * // append each parent's props in turn: + * props.append(o.visible, o.alpha, o.shadow, o.compositeOperation, o.getMatrix()); + * o = o.parent; + * } while (o); + * @method prepend + * @param {Boolean} visible desired visible value + * @param {Number} alpha desired alpha value + * @param {Shadow} shadow desired shadow value + * @param {String} compositeOperation desired composite operation value + * @param {Matrix2D} [matrix] a Matrix2D instance + * @return {DisplayProps} This instance. Useful for chaining method calls. + */ + p.prepend = function(visible, alpha, shadow, compositeOperation, matrix) { + this.alpha *= alpha; + this.shadow = shadow || this.shadow; + this.compositeOperation = compositeOperation || this.compositeOperation; + this.visible = this.visible && visible; + matrix&&this.matrix.prependMatrix(matrix); + return this; + }; + + /** + * Appends the specified display properties. This is generally used to apply a child's properties its parent's. + * @method append + * @param {Boolean} visible desired visible value + * @param {Number} alpha desired alpha value + * @param {Shadow} shadow desired shadow value + * @param {String} compositeOperation desired composite operation value + * @param {Matrix2D} [matrix] a Matrix2D instance + * @return {DisplayProps} This instance. Useful for chaining method calls. + */ + p.append = function(visible, alpha, shadow, compositeOperation, matrix) { + this.alpha *= alpha; + this.shadow = this.shadow || shadow; + this.compositeOperation = this.compositeOperation || compositeOperation; + this.visible = this.visible && visible; + matrix&&this.matrix.appendMatrix(matrix); + return this; + }; + + /** + * Resets this instance and its matrix to default values. + * @method identity + * @return {DisplayProps} This instance. Useful for chaining method calls. + */ + p.identity = function() { + this.visible = true; + this.alpha = 1; + this.shadow = this.compositeOperation = null; + this.matrix.identity(); + return this; + }; + + /** + * Returns a clone of the DisplayProps instance. Clones the associated matrix. + * @method clone + * @return {DisplayProps} a clone of the DisplayProps instance. **/ - p.y = 0; + p.clone = function() { + return new DisplayProps(this.alpha, this.shadow, this.compositeOperation, this.visible, this.matrix.clone()); + }; + +// private methods: + + createjs.DisplayProps = DisplayProps; +})(); + +//############################################################################## +// Point.js +//############################################################################## + +this.createjs = this.createjs||{}; + +(function() { + "use strict"; + // constructor: + /** + * Represents a point on a 2 dimensional x / y coordinate system. + * + *

Example

+ * + * var point = new createjs.Point(0, 100); + * + * @class Point + * @param {Number} [x=0] X position. + * @param {Number} [y=0] Y position. + * @constructor + **/ + function Point(x, y) { + this.setValues(x, y); + + + // public properties: + // assigned in the setValues method. + /** + * X position. + * @property x + * @type Number + **/ + + /** + * Y position. + * @property y + * @type Number + **/ + } + var p = Point.prototype; + + +// public methods: /** - * Initialization method. Can also be used to reinitialize the instance. - * @method initialize + * Sets the specified values on this instance. + * @method setValues * @param {Number} [x=0] X position. * @param {Number} [y=0] Y position. * @return {Point} This instance. Useful for chaining method calls. */ - p.initialize = function(x, y) { - this.x = (x == null ? 0 : x); - this.y = (y == null ? 0 : y); + p.setValues = function(x, y) { + this.x = x||0; + this.y = y||0; return this; }; -// public methods: /** * Copies all properties from the specified point to this point. * @method copy @@ -2329,7 +2181,9 @@ var p = Point.prototype; * @return {Point} This point. Useful for chaining method calls. */ p.copy = function(point) { - return this.initialize(point.x, point.y); + this.x = point.x; + this.y = point.y; + return this; }; /** @@ -2350,104 +2204,80 @@ var p = Point.prototype; return "[Point (x="+this.x+" y="+this.y+")]"; }; -createjs.Point = Point; + + createjs.Point = Point; }()); -/* -* Rectangle -* Visit http://createjs.com/ for documentation, updates and examples. -* -* Copyright (c) 2010 gskinner.com, inc. -* -* Permission is hereby granted, free of charge, to any person -* obtaining a copy of this software and associated documentation -* files (the "Software"), to deal in the Software without -* restriction, including without limitation the rights to use, -* copy, modify, merge, publish, distribute, sublicense, and/or sell -* copies of the Software, and to permit persons to whom the -* Software is furnished to do so, subject to the following -* conditions: -* -* The above copyright notice and this permission notice shall be -* included in all copies or substantial portions of the Software. -* -* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES -* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT -* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR -* OTHER DEALINGS IN THE SOFTWARE. -*/ -/** - * @module EaselJS - */ +//############################################################################## +// Rectangle.js +//############################################################################## -// namespace: this.createjs = this.createjs||{}; (function() { "use strict"; -/** - * Represents a rectangle as defined by the points (x, y) and (x+width, y+height). - * - * @example - * var rect = new createjs.Rectangle(0, 0, 100, 100); - * - * @class Rectangle - * @param {Number} [x=0] X position. - * @param {Number} [y=0] Y position. - * @param {Number} [width=0] The width of the Rectangle. - * @param {Number} [height=0] The height of the Rectangle. - * @constructor - **/ -var Rectangle = function(x, y, width, height) { - this.initialize(x, y, width, height); -}; -var p = Rectangle.prototype; - -// public properties: - /** - * X position. - * @property x - * @type Number - **/ - p.x = 0; - - /** - * Y position. - * @property y - * @type Number - **/ - p.y = 0; - - /** - * Width. - * @property width - * @type Number - **/ - p.width = 0; - - /** - * Height. - * @property height - * @type Number - **/ - p.height = 0; // constructor: + /** + * Represents a rectangle as defined by the points (x, y) and (x+width, y+height). + * + *

Example

+ * + * var rect = new createjs.Rectangle(0, 0, 100, 100); + * + * @class Rectangle + * @param {Number} [x=0] X position. + * @param {Number} [y=0] Y position. + * @param {Number} [width=0] The width of the Rectangle. + * @param {Number} [height=0] The height of the Rectangle. + * @constructor + **/ + function Rectangle(x, y, width, height) { + this.setValues(x, y, width, height); + + + // public properties: + // assigned in the setValues method. + /** + * X position. + * @property x + * @type Number + **/ + + /** + * Y position. + * @property y + * @type Number + **/ + + /** + * Width. + * @property width + * @type Number + **/ + + /** + * Height. + * @property height + * @type Number + **/ + } + var p = Rectangle.prototype; + + +// public methods: /** - * Initialization method. Can also be used to reinitialize the instance. - * @method initialize + * Sets the specified values on this instance. + * @method setValues * @param {Number} [x=0] X position. * @param {Number} [y=0] Y position. * @param {Number} [width=0] The width of the Rectangle. * @param {Number} [height=0] The height of the Rectangle. * @return {Rectangle} This instance. Useful for chaining method calls. */ - p.initialize = function(x, y, width, height) { + p.setValues = function(x, y, width, height) { + // don't forget to update docs in the constructor if these change: this.x = x||0; this.y = y||0; this.width = width||0; @@ -2455,7 +2285,85 @@ var p = Rectangle.prototype; return this; }; -// public methods: + /** + * Extends the rectangle's bounds to include the described point or rectangle. + * @method extend + * @param {Number} x X position of the point or rectangle. + * @param {Number} y Y position of the point or rectangle. + * @param {Number} [width=0] The width of the rectangle. + * @param {Number} [height=0] The height of the rectangle. + * @return {Rectangle} This instance. Useful for chaining method calls. + */ + p.extend = function(x, y, width, height) { + width = width||0; + height = height||0; + if (x+width > this.x+this.width) { this.width = x+width-this.x; } + if (y+height > this.y+this.height) { this.height = y+height-this.y; } + if (x < this.x) { this.width += this.x-x; this.x = x; } + if (y < this.y) { this.height += this.y-y; this.y = y; } + return this; + }; + + /** + * Returns true if this rectangle fully encloses the described point or rectangle. + * @method contains + * @param {Number} x X position of the point or rectangle. + * @param {Number} y Y position of the point or rectangle. + * @param {Number} [width=0] The width of the rectangle. + * @param {Number} [height=0] The height of the rectangle. + * @return {Boolean} True if the described point or rectangle is contained within this rectangle. + */ + p.contains = function(x, y, width, height) { + width = width||0; + height = height||0; + return (x >= this.x && x+width <= this.x+this.width && y >= this.y && y+height <= this.y+this.height); + }; + + /** + * Returns a new rectangle which contains this rectangle and the specified rectangle. + * @method union + * @param {Rectangle} rect The rectangle to calculate a union with. + * @return {Rectangle} A new rectangle describing the union. + */ + p.union = function(rect) { + return this.clone().extend(rect.x, rect.y, rect.width, rect.height); + }; + + /** + * Returns a new rectangle which describes the intersection (overlap) of this rectangle and the specified rectangle, + * or null if they do not intersect. + * @method intersection + * @param {Rectangle} rect The rectangle to calculate an intersection with. + * @return {Rectangle} A new rectangle describing the intersection or null. + */ + p.intersection = function(rect) { + var x1 = rect.x, y1 = rect.y, x2 = x1+rect.width, y2 = y1+rect.height; + if (this.x > x1) { x1 = this.x; } + if (this.y > y1) { y1 = this.y; } + if (this.x + this.width < x2) { x2 = this.x + this.width; } + if (this.y + this.height < y2) { y2 = this.y + this.height; } + return (x2 <= x1 || y2 <= y1) ? null : new Rectangle(x1, y1, x2-x1, y2-y1); + }; + + /** + * Returns true if the specified rectangle intersects (has any overlap) with this rectangle. + * @method intersects + * @param {Rectangle} rect The rectangle to compare. + * @return {Boolean} True if the rectangles intersect. + */ + p.intersects = function(rect) { + return (rect.x <= this.x+this.width && this.x <= rect.x+rect.width && rect.y <= this.y+this.height && this.y <= rect.y + rect.height); + }; + + /** + * Returns true if the width or height are equal or less than 0. + * @method isEmpty + * @return {Boolean} True if the rectangle is empty. + */ + p.isEmpty = function() { + return this.width <= 0 || this.height <= 0; + }; + /** * Copies all properties from the specified rectangle to this rectangle. * @method copy @@ -2463,7 +2371,7 @@ var p = Rectangle.prototype; * @return {Rectangle} This rectangle. Useful for chaining method calls. */ p.copy = function(rectangle) { - return this.initialize(rectangle.x, rectangle.y, rectangle.width, rectangle.height); + return this.setValues(rectangle.x, rectangle.y, rectangle.width, rectangle.height); }; /** @@ -2484,124 +2392,136 @@ var p = Rectangle.prototype; return "[Rectangle (x="+this.x+" y="+this.y+" width="+this.width+" height="+this.height+")]"; }; -createjs.Rectangle = Rectangle; + + createjs.Rectangle = Rectangle; }()); -/* -* ButtonHelper -* Visit http://createjs.com/ for documentation, updates and examples. -* -* Copyright (c) 2010 gskinner.com, inc. -* -* Permission is hereby granted, free of charge, to any person -* obtaining a copy of this software and associated documentation -* files (the "Software"), to deal in the Software without -* restriction, including without limitation the rights to use, -* copy, modify, merge, publish, distribute, sublicense, and/or sell -* copies of the Software, and to permit persons to whom the -* Software is furnished to do so, subject to the following -* conditions: -* -* The above copyright notice and this permission notice shall be -* included in all copies or substantial portions of the Software. -* -* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES -* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT -* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR -* OTHER DEALINGS IN THE SOFTWARE. -*/ -/** - * @module EaselJS - */ +//############################################################################## +// ButtonHelper.js +//############################################################################## -// namespace: this.createjs = this.createjs||{}; (function() { "use strict"; -/** - * The ButtonHelper is a helper class to create interactive buttons from {{#crossLink "MovieClip"}}{{/crossLink}} or - * {{#crossLink "Sprite"}}{{/crossLink}} instances. This class will intercept mouse events from an object, and - * automatically call {{#crossLink "Sprite/gotoAndStop"}}{{/crossLink}} or {{#crossLink "Sprite/gotoAndPlay"}}{{/crossLink}}, - * to the respective animation labels, add a pointer cursor, and allows the user to define a hit state frame. - * - * The ButtonHelper instance does not need to be added to the stage, but a reference should be maintained to prevent - * garbage collection. - * - * Note that over states will not work unless you call {{#crossLink "Stage/enableMouseOver"}}{{/crossLink}}. - * - *

Example

- * - * var helper = new createjs.ButtonHelper(myInstance, "out", "over", "down", false, myInstance, "hit"); - * myInstance.addEventListener("click", handleClick); - * function handleClick(event) { - * // Click Happened. - * } - * - * @class ButtonHelper - * @param {Sprite|MovieClip} target The instance to manage. - * @param {String} [outLabel="out"] The label or animation to go to when the user rolls out of the button. - * @param {String} [overLabel="over"] The label or animation to go to when the user rolls over the button. - * @param {String} [downLabel="down"] The label or animation to go to when the user presses the button. - * @param {Boolean} [play=false] If the helper should call "gotoAndPlay" or "gotoAndStop" on the button when changing - * states. - * @param {DisplayObject} [hitArea] An optional item to use as the hit state for the button. If this is not defined, - * then the button's visible states will be used instead. Note that the same instance as the "target" argument can be - * used for the hitState. - * @param {String} [hitLabel] The label or animation on the hitArea instance that defines the hitArea bounds. If this is - * null, then the default state of the hitArea will be used. * - * @constructor - */ -var ButtonHelper = function(target, outLabel, overLabel, downLabel, play, hitArea, hitLabel) { - this.initialize(target, outLabel, overLabel, downLabel, play, hitArea, hitLabel); -}; -var p = ButtonHelper.prototype; -// public properties: +// constructor: /** - * The target for this button helper. - * @property target - * @type MovieClip | Sprite - * @readonly - **/ - p.target = null; + * The ButtonHelper is a helper class to create interactive buttons from {{#crossLink "MovieClip"}}{{/crossLink}} or + * {{#crossLink "Sprite"}}{{/crossLink}} instances. This class will intercept mouse events from an object, and + * automatically call {{#crossLink "Sprite/gotoAndStop"}}{{/crossLink}} or {{#crossLink "Sprite/gotoAndPlay"}}{{/crossLink}}, + * to the respective animation labels, add a pointer cursor, and allows the user to define a hit state frame. + * + * The ButtonHelper instance does not need to be added to the stage, but a reference should be maintained to prevent + * garbage collection. + * + * Note that over states will not work unless you call {{#crossLink "Stage/enableMouseOver"}}{{/crossLink}}. + * + *

Example

+ * + * var helper = new createjs.ButtonHelper(myInstance, "out", "over", "down", false, myInstance, "hit"); + * myInstance.addEventListener("click", handleClick); + * function handleClick(event) { + * // Click Happened. + * } + * + * @class ButtonHelper + * @param {Sprite|MovieClip} target The instance to manage. + * @param {String} [outLabel="out"] The label or animation to go to when the user rolls out of the button. + * @param {String} [overLabel="over"] The label or animation to go to when the user rolls over the button. + * @param {String} [downLabel="down"] The label or animation to go to when the user presses the button. + * @param {Boolean} [play=false] If the helper should call "gotoAndPlay" or "gotoAndStop" on the button when changing + * states. + * @param {DisplayObject} [hitArea] An optional item to use as the hit state for the button. If this is not defined, + * then the button's visible states will be used instead. Note that the same instance as the "target" argument can be + * used for the hitState. + * @param {String} [hitLabel] The label or animation on the hitArea instance that defines the hitArea bounds. If this is + * null, then the default state of the hitArea will be used. * + * @constructor + */ + function ButtonHelper(target, outLabel, overLabel, downLabel, play, hitArea, hitLabel) { + if (!target.addEventListener) { return; } + + + // public properties: + /** + * The target for this button helper. + * @property target + * @type MovieClip | Sprite + * @readonly + **/ + this.target = target; + + /** + * The label name or frame number to display when the user mouses out of the target. Defaults to "over". + * @property overLabel + * @type String | Number + **/ + this.overLabel = overLabel == null ? "over" : overLabel; + + /** + * The label name or frame number to display when the user mouses over the target. Defaults to "out". + * @property outLabel + * @type String | Number + **/ + this.outLabel = outLabel == null ? "out" : outLabel; + + /** + * The label name or frame number to display when the user presses on the target. Defaults to "down". + * @property downLabel + * @type String | Number + **/ + this.downLabel = downLabel == null ? "down" : downLabel; + + /** + * If true, then ButtonHelper will call gotoAndPlay, if false, it will use gotoAndStop. Default is false. + * @property play + * @default false + * @type Boolean + **/ + this.play = play; + + + // private properties + /** + * @property _isPressed + * @type Boolean + * @protected + **/ + this._isPressed = false; + + /** + * @property _isOver + * @type Boolean + * @protected + **/ + this._isOver = false; + + /** + * @property _enabled + * @type Boolean + * @protected + **/ + this._enabled = false; + + + // setup: + target.mouseChildren = false; // prevents issues when children are removed from the display list when state changes. + this.enabled = true; + this.handleEvent({}); + if (hitArea) { + if (hitLabel) { + hitArea.actionsEnabled = false; + hitArea.gotoAndStop&&hitArea.gotoAndStop(hitLabel); + } + target.hitArea = hitArea; + } + } + var p = ButtonHelper.prototype; - /** - * The label name or frame number to display when the user mouses out of the target. Defaults to "over". - * @property overLabel - * @type String | Number - **/ - p.overLabel = null; - - /** - * The label name or frame number to display when the user mouses over the target. Defaults to "out". - * @property outLabel - * @type String | Number - **/ - p.outLabel = null; - - /** - * The label name or frame number to display when the user presses on the target. Defaults to "down". - * @property downLabel - * @type String | Number - **/ - p.downLabel = null; - - /** - * If true, then ButtonHelper will call gotoAndPlay, if false, it will use gotoAndStop. Default is false. - * @property play - * @default false - * @type Boolean - **/ - p.play = false; // getter / setters: - /** * Enables or disables the button functionality on the target. * @property enabled @@ -2614,6 +2534,7 @@ var p = ButtonHelper.prototype; * @param {Boolean} value **/ p.setEnabled = function(value) { // TODO: deprecated. + if (value == this._enabled) { return; } var o = this.target; this._enabled = value; if (value) { @@ -2646,66 +2567,8 @@ var p = ButtonHelper.prototype; }); } catch (e) {} // TODO: use Log -// private properties - /** - * @property _isPressed - * @type Boolean - * @protected - **/ - p._isPressed = false; - - /** - * @property _isOver - * @type Boolean - * @protected - **/ - p._isOver = false; - - /** - * @property _enabled - * @type Boolean - * @protected - **/ - p._enabled = false; - -// constructor: - /** - * Initialization method. - * @method initialize - * @param {Sprite|MovieClip} target The instance to manage. - * @param {String} [outLabel="out"] The label or animation to go to when the user rolls out of the button. - * @param {String} [overLabel="over"] The label or animation to go to when the user rolls over the button. - * @param {String} [downLabel="down"] The label or animation to go to when the user presses the button. - * @param {Boolean} [play=false] If the helper should call "gotoAndPlay" or "gotoAndStop" on the button when changing - * states. - * @param {DisplayObject} [hitArea] An optional item to use as the hit state for the button. If this is not defined, - * then the button's visible states will be used instead. Note that the same instance as the "target" argument can be - * used for the hitState. - * @param {String} [hitLabel] The label or animation on the hitArea instance that defines the hitArea bounds. If this is - * null, then the default state of the hitArea will be used. - * @protected - **/ - p.initialize = function(target, outLabel, overLabel, downLabel, play, hitArea, hitLabel) { - if (!target.addEventListener) { return; } - this.target = target; - target.mouseChildren = false; // prevents issues when children are removed from the display list when state changes. - this.overLabel = overLabel == null ? "over" : overLabel; - this.outLabel = outLabel == null ? "out" : outLabel; - this.downLabel = downLabel == null ? "down" : downLabel; - this.play = play; - this.setEnabled(true); - this.handleEvent({}); - if (hitArea) { - if (hitLabel) { - hitArea.actionsEnabled = false; - hitArea.gotoAndStop&&hitArea.gotoAndStop(hitLabel); - } - target.hitArea = hitArea; - } - }; // public methods: - /** * Returns a string representation of this object. * @method toString @@ -2716,7 +2579,7 @@ var p = ButtonHelper.prototype; }; -// protected methods: +// private methods: /** * @method handleEvent * @param {Object} evt The mouse event to handle. @@ -2744,64 +2607,70 @@ var p = ButtonHelper.prototype; } }; -createjs.ButtonHelper = ButtonHelper; + + createjs.ButtonHelper = ButtonHelper; }()); -/* -* Shadow -* Visit http://createjs.com/ for documentation, updates and examples. -* -* Copyright (c) 2010 gskinner.com, inc. -* -* Permission is hereby granted, free of charge, to any person -* obtaining a copy of this software and associated documentation -* files (the "Software"), to deal in the Software without -* restriction, including without limitation the rights to use, -* copy, modify, merge, publish, distribute, sublicense, and/or sell -* copies of the Software, and to permit persons to whom the -* Software is furnished to do so, subject to the following -* conditions: -* -* The above copyright notice and this permission notice shall be -* included in all copies or substantial portions of the Software. -* -* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES -* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT -* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR -* OTHER DEALINGS IN THE SOFTWARE. -*/ -/** - * @module EaselJS - */ +//############################################################################## +// Shadow.js +//############################################################################## -// namespace: this.createjs = this.createjs||{}; (function() { "use strict"; -/** - * This class encapsulates the properties required to define a shadow to apply to a {{#crossLink "DisplayObject"}}{{/crossLink}} - * via its shadow property. - * - *

Example

- * myImage.shadow = new createjs.Shadow("#000000", 5, 5, 10); - * - * @class Shadow - * @constructor - * @param {String} color The color of the shadow. - * @param {Number} offsetX The x offset of the shadow in pixels. - * @param {Number} offsetY The y offset of the shadow in pixels. - * @param {Number} blur The size of the blurring effect. - **/ -var Shadow = function(color, offsetX, offsetY, blur) { - this.initialize(color, offsetX, offsetY, blur); -}; -var p = Shadow.prototype; + +// constructor: + /** + * This class encapsulates the properties required to define a shadow to apply to a {{#crossLink "DisplayObject"}}{{/crossLink}} + * via its shadow property. + * + *

Example

+ * + * myImage.shadow = new createjs.Shadow("#000000", 5, 5, 10); + * + * @class Shadow + * @constructor + * @param {String} color The color of the shadow. + * @param {Number} offsetX The x offset of the shadow in pixels. + * @param {Number} offsetY The y offset of the shadow in pixels. + * @param {Number} blur The size of the blurring effect. + **/ + function Shadow(color, offsetX, offsetY, blur) { + + + // public properties: + /** The color of the shadow. + * property color + * @type String + * @default null + */ + this.color = color||"black"; + + /** The x offset of the shadow. + * property offsetX + * @type Number + * @default 0 + */ + this.offsetX = offsetX||0; + + /** The y offset of the shadow. + * property offsetY + * @type Number + * @default 0 + */ + this.offsetY = offsetY||0; + + /** The blur of the shadow. + * property blur + * @type Number + * @default 0 + */ + this.blur = blur||0; + } + var p = Shadow.prototype; + // static public properties: /** @@ -2812,53 +2681,8 @@ var p = Shadow.prototype; * @final * @readonly **/ - Shadow.identity = null; // set at bottom of class definition. + Shadow.identity = new Shadow("transparent", 0, 0, 0); -// public properties: - /** The color of the shadow. - * property color - * @type String - * @default null - */ - p.color = null; - - /** The x offset of the shadow. - * property offsetX - * @type Number - * @default 0 - */ - p.offsetX = 0; - - /** The y offset of the shadow. - * property offsetY - * @type Number - * @default 0 - */ - p.offsetY = 0; - - /** The blur of the shadow. - * property blur - * @type Number - * @default 0 - */ - p.blur = 0; - -// constructor: - /** - * Initialization method. - * @method initialize - * @protected - * @param {String} color The color of the shadow. - * @param {Number} offsetX The x offset of the shadow. - * @param {Number} offsetY The y offset of the shadow. - * @param {Number} blur The size of the blurring effect. - **/ - p.initialize = function(color, offsetX, offsetY, blur) { - this.color = color; - this.offsetX = offsetX; - this.offsetY = offsetY; - this.blur = blur; - }; // public methods: /** @@ -2870,7 +2694,6 @@ var p = Shadow.prototype; return "[Shadow]"; }; - /** * Returns a clone of this Shadow instance. * @method clone @@ -2879,269 +2702,354 @@ var p = Shadow.prototype; p.clone = function() { return new Shadow(this.color, this.offsetX, this.offsetY, this.blur); }; + - // this has to be populated after the class is defined: - Shadow.identity = new Shadow("transparent", 0, 0, 0); - -createjs.Shadow = Shadow; + createjs.Shadow = Shadow; }()); -/* -* SpriteSheet -* Visit http://createjs.com/ for documentation, updates and examples. -* -* Copyright (c) 2010 gskinner.com, inc. -* -* Permission is hereby granted, free of charge, to any person -* obtaining a copy of this software and associated documentation -* files (the "Software"), to deal in the Software without -* restriction, including without limitation the rights to use, -* copy, modify, merge, publish, distribute, sublicense, and/or sell -* copies of the Software, and to permit persons to whom the -* Software is furnished to do so, subject to the following -* conditions: -* -* The above copyright notice and this permission notice shall be -* included in all copies or substantial portions of the Software. -* -* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES -* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT -* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR -* OTHER DEALINGS IN THE SOFTWARE. -*/ -/** - * @module EaselJS - */ +//############################################################################## +// SpriteSheet.js +//############################################################################## -// namespace: this.createjs = this.createjs||{}; (function() { "use strict"; -/** - * Encapsulates the properties and methods associated with a sprite sheet. A sprite sheet is a series of images (usually - * animation frames) combined into a larger image (or images). For example, an animation consisting of eight 100x100 - * images could be combined into a single 400x200 sprite sheet (4 frames across by 2 high). - * - * The data passed to the SpriteSheet constructor defines three critical pieces of information:
    - *
  1. The image or images to use.
  2. - *
  3. The positions of individual image frames. This data can be represented in one of two ways: - * As a regular grid of sequential, equal-sized frames, or as individually defined, variable sized frames arranged in - * an irregular (non-sequential) fashion.
  4. - *
  5. Likewise, animations can be represented in two ways: As a series of sequential frames, defined by a start and - * end frame [0,3], or as a list of frames [0,1,2,3].
  6. - *
- * - *

SpriteSheet Format

- * - * data = { - * // DEFINING FRAMERATE: - * // this specifies the framerate that will be set on the SpriteSheet. See {{#crossLink "SpriteSheet/framerate:property"}}{{/crossLink}} - * // for more information. - * framerate: 20, - * - * // DEFINING IMAGES: - * // list of images or image URIs to use. SpriteSheet can handle preloading. - * // the order dictates their index value for frame definition. - * images: [image1, "path/to/image2.png"], - * - * // DEFINING FRAMES: - * // the simple way to define frames, only requires frame size because frames are consecutive: - * // define frame width/height, and optionally the frame count and registration point x/y. - * // if count is omitted, it will be calculated automatically based on image dimensions. - * frames: {width:64, height:64, count:20, regX: 32, regY:64}, - * - * // OR, the complex way that defines individual rects for frames. - * // The 5th value is the image index per the list defined in "images" (defaults to 0). - * frames: [ - * // x, y, width, height, imageIndex, regX, regY - * [0,0,64,64,0,32,64], - * [64,0,96,64,0] - * ], - * - * // DEFINING ANIMATIONS: - * - * // simple animation definitions. Define a consecutive range of frames (begin to end inclusive). - * // optionally define a "next" animation to sequence to (or false to stop) and a playback "speed". - * animations: { - * // start, end, next, speed - * run: [0,8], - * jump: [9,12,"run",2] - * } - * - * // the complex approach which specifies every frame in the animation by index. - * animations: { - * run: { - * frames: [1,2,3,3,2,1] - * }, - * jump: { - * frames: [1,4,5,6,1], - * next: "run", - * speed: 2 - * }, - * stand: { frames: [7] } - * } - * - * // the above two approaches can be combined, you can also use a single frame definition: - * animations: { - * run: [0,8,true,2], - * jump: { - * frames: [8,9,10,9,8], - * next: "run", - * speed: 2 - * }, - * stand: 7 - * } - * } - * - * Note that the speed property was added in EaselJS 0.7.0. Earlier versions had a frequency - * property instead, which was the inverse of speed. For example, a value of "4" would be 1/4 normal speed in earlier - * versions, but us 4x normal speed in 0.7.0+. - * - *

Example

- * To define a simple sprite sheet, with a single image "sprites.jpg" arranged in a regular 50x50 grid with two - * animations, "run" looping from frame 0-4 inclusive, and "jump" playing from frame 5-8 and sequencing back to run: - * - * var data = { - * images: ["sprites.jpg"], - * frames: {width:50, height:50}, - * animations: {run:[0,4], jump:[5,8,"run"]} - * }; - * var spriteSheet = new createjs.SpriteSheet(data); - * var animation = new createjs.Sprite(spriteSheet, "run"); - * - * - * Warning: Images loaded cross-origin will throw cross-origin security errors when interacted with - * using a mouse, using methods such as `getObjectUnderPoint`, using filters, or caching. You can get around this by - * setting `crossOrigin` flags on your images before passing them to EaselJS, eg: `img.crossOrigin="Anonymous";` - * - * @class SpriteSheet - * @constructor - * @param {Object} data An object describing the SpriteSheet data. - * @extends EventDispatcher - **/ -var SpriteSheet = function(data) { - this.initialize(data); -}; -var p = SpriteSheet.prototype = new createjs.EventDispatcher(); + + +// constructor: + /** + * Encapsulates the properties and methods associated with a sprite sheet. A sprite sheet is a series of images (usually + * animation frames) combined into a larger image (or images). For example, an animation consisting of eight 100x100 + * images could be combined into a single 400x200 sprite sheet (4 frames across by 2 high). + * + * The data passed to the SpriteSheet constructor defines:
    + *
  1. The source image or images to use.
  2. + *
  3. The positions of individual image frames.
  4. + *
  5. Sequences of frames that form named animations. Optional.
  6. + *
  7. The target playback framerate. Optional.
  8. + *
+ * + *

SpriteSheet Format

+ * + * SpriteSheets are an object with two required properties (`images` and `frames`), and two optional properties + * (`framerate` and `animations`). This makes them easy to define in javascript code, or in JSON. + * + *

images

+ * An array of source images. Images can be either an HTMLImage + * instance, or a uri to an image. The former is recommended to control preloading. + * + * images: [image1, "path/to/image2.png"], + * + *

frames

+ * Defines the individual frames. There are two supported formats for frame data:
    + *
  1. when all of the frames are the same size (in a grid), use an object with `width`, `height`, `regX`, `regY`, and `count` properties. + * `width` & `height` are required and specify the dimensions of the frames. + * `regX` & `regY` indicate the registration point or "origin" of the frames. + * `count` allows you to specify the total number of frames in the spritesheet; if omitted, this will be calculated + * based on the dimensions of the source images and the frames. Frames will be assigned indexes based on their position + * in the source images (left to right, top to bottom). + * + * frames: {width:64, height:64, count:20, regX: 32, regY:64} + * + *
  2. if the frames are of different sizes, use an array of frame definitions. Each definition is itself an array + * with 4 required and 3 optional entries, in the order: `x`, `y`, `width`, `height`, `imageIndex`, `regX`, `regY`. The first + * four entries are required and define the frame rectangle. The fifth specifies the index of the source image (defaults to 0). The + * last two specify the registration point of the frame. + * + * frames: [ + * // x, y, width, height, imageIndex*, regX*, regY* + * [64, 0, 96, 64], + * [0, 0, 64, 64, 1, 32, 32] + * // etc. + * ] + * + *
+ * + *

animations

+ * Optional. An object defining sequences of frames to play as named animations. Each property corresponds to an + * animation of the same name. Each animation must specify the frames to play, and may + * also include a relative playback `speed` (ex. 2 would playback at double speed, 0.5 at half), and + * the name of the `next` animation to sequence to after it completes. + * + * There are three formats supported for defining the frames in an animation, which can be mixed and matched as appropriate:
    + *
  1. for a single frame animation, you can simply specify the frame index + * + * animations: { + * sit: 7 + * } + * + *
  2. for an animation of consecutive frames, you can use an array with two required, and two optional entries + * in the order: `start`, `end`, `next`, and `speed`. This will play the frames from start to end inclusive. + * + * animations: { + * // start, end, next*, speed* + * run: [0, 8], + * jump: [9, 12, "run", 2] + * } + * + *
  3. for non-consecutive frames, you can use an object with a `frames` property defining an array of frame indexes to + * play in order. The object can also specify `next` and `speed` properties. + * + * animations: { + * walk: { + * frames: [1,2,3,3,2,1] + * }, + * shoot: { + * frames: [1,4,5,6], + * next: "walk", + * speed: 0.5 + * } + * } + * + *
+ * Note: the `speed` property was added in EaselJS 0.7.0. Earlier versions had a `frequency` + * property instead, which was the inverse of `speed`. For example, a value of "4" would be 1/4 normal speed in earlier + * versions, but is 4x normal speed in 0.7.0+. + * + *

framerate

+ * Optional. Indicates the default framerate to play this spritesheet at in frames per second. + * See {{#crossLink "SpriteSheet/framerate:property"}}{{/crossLink}} for more information. + * + * framerate: 20 + * + *

Example

+ * To define a simple sprite sheet, with a single image "sprites.jpg" arranged in a regular 50x50 grid with three + * animations: "stand" showing the first frame, "run" looping frame 1-5 inclusive, and "jump" playing frame 6-8 and sequencing back to run. + * + * var data = { + * images: ["sprites.jpg"], + * frames: {width:50, height:50}, + * animations: { + * stand:0, + * run:[1,5], + * jump:[6,8,"run"] + * } + * }; + * var spriteSheet = new createjs.SpriteSheet(data); + * var animation = new createjs.Sprite(spriteSheet, "run"); + * + * + * Warning: Images loaded cross-origin will throw cross-origin security errors when interacted with + * using a mouse, using methods such as `getObjectUnderPoint`, using filters, or caching. You can get around this by + * setting `crossOrigin` flags on your images before passing them to EaselJS, eg: `img.crossOrigin="Anonymous";` + * + * @class SpriteSheet + * @constructor + * @param {Object} data An object describing the SpriteSheet data. + * @extends EventDispatcher + **/ + function SpriteSheet(data) { + this.EventDispatcher_constructor(); + + + // public properties: + /** + * Indicates whether all images are finished loading. + * @property complete + * @type Boolean + * @readonly + **/ + this.complete = true; + + /** + * Specifies the framerate to use by default for Sprite instances using the SpriteSheet. See + * Sprite.framerate for more information. + * @property framerate + * @type Number + **/ + this.framerate = 0; + + + // private properties: + /** + * @property _animations + * @protected + **/ + this._animations = null; + + /** + * @property _frames + * @protected + **/ + this._frames = null; + + /** + * @property _images + * @protected + **/ + this._images = null; + + /** + * @property _data + * @protected + **/ + this._data = null; + + /** + * @property _loadCount + * @protected + **/ + this._loadCount = 0; + + // only used for simple frame defs: + /** + * @property _frameHeight + * @protected + **/ + this._frameHeight = 0; + + /** + * @property _frameWidth + * @protected + **/ + this._frameWidth = 0; + + /** + * @property _numFrames + * @protected + **/ + this._numFrames = 0; + + /** + * @property _regX + * @protected + **/ + this._regX = 0; + + /** + * @property _regY + * @protected + **/ + this._regY = 0; + + + // setup: + this._parseData(data); + } + var p = createjs.extend(SpriteSheet, createjs.EventDispatcher); + // events: - /** * Dispatched when all images are loaded. Note that this only fires if the images * were not fully loaded when the sprite sheet was initialized. You should check the complete property * to prior to adding a listener. Ex. - *
var sheet = new SpriteSheet(data);
-	 * if (!sheet.complete) {
-	 *    // not preloaded, listen for the complete event:
-	 *    sheet.addEventListener("complete", handler);
-	 * }
+ * + * var sheet = new SpriteSheet(data); + * if (!sheet.complete) { + * // not preloaded, listen for the complete event: + * sheet.addEventListener("complete", handler); + * } + * * @event complete * @param {Object} target The object that dispatched the event. * @param {String} type The event type. * @since 0.6.0 */ + -// public properties: +// public methods: /** - * Indicates whether all images are finished loading. - * @property complete - * @type Boolean - * @readonly + * Returns the total number of frames in the specified animation, or in the whole sprite + * sheet if the animation param is omitted. Returns 0 if the spritesheet relies on calculated frame counts, and + * the images have not been fully loaded. + * @method getNumFrames + * @param {String} animation The name of the animation to get a frame count for. + * @return {Number} The number of frames in the animation, or in the entire sprite sheet if the animation param is omitted. + */ + p.getNumFrames = function(animation) { + if (animation == null) { + return this._frames ? this._frames.length : this._numFrames || 0; + } else { + var data = this._data[animation]; + if (data == null) { return 0; } + else { return data.frames.length; } + } + }; + + /** + * Returns an array of all available animation names as strings. + * @method getAnimations + * @return {Array} an array of animation names available on this sprite sheet. **/ - p.complete = true; - + p.getAnimations = function() { + return this._animations.slice(0); + }; /** - * Specifies the framerate to use by default for Sprite instances using the SpriteSheet. See - * Sprite.framerate for more information. - * @property framerate - * @type Number + * Returns an object defining the specified animation. The returned object contains: + * @method getAnimation + * @param {String} name The name of the animation to get. + * @return {Object} a generic object with frames, speed, name, and next properties. **/ - p.framerate = 0; + p.getAnimation = function(name) { + return this._data[name]; + }; - // TODO: deprecated. /** - * REMOVED. Use {{#crossLink "EventDispatcher/addEventListener"}}{{/crossLink}} and the {{#crossLink "SpriteSheet/complete:event"}}{{/crossLink}} - * event. - * @property onComplete - * @type Function - * @deprecated Use addEventListener and the "complete" event. + * Returns an object specifying the image and source rect of the specified frame. The returned object has: + * @method getFrame + * @param {Number} frameIndex The index of the frame. + * @return {Object} a generic object with image and rect properties. Returns null if the frame does not exist. **/ + p.getFrame = function(frameIndex) { + var frame; + if (this._frames && (frame=this._frames[frameIndex])) { return frame; } + return null; + }; -// private properties: /** - * @property _animations - * @protected + * Returns a {{#crossLink "Rectangle"}}{{/crossLink}} instance defining the bounds of the specified frame relative + * to the origin. For example, a 90 x 70 frame with a regX of 50 and a regY of 40 would return: + * + * [x=-50, y=-40, width=90, height=70] + * + * @method getFrameBounds + * @param {Number} frameIndex The index of the frame. + * @param {Rectangle} [rectangle] A Rectangle instance to copy the values into. By default a new instance is created. + * @return {Rectangle} A Rectangle instance. Returns null if the frame does not exist, or the image is not fully loaded. **/ - p._animations = null; + p.getFrameBounds = function(frameIndex, rectangle) { + var frame = this.getFrame(frameIndex); + return frame ? (rectangle||new createjs.Rectangle()).setValues(-frame.regX, -frame.regY, frame.rect.width, frame.rect.height) : null; + }; /** - * @property _frames - * @protected + * Returns a string representation of this object. + * @method toString + * @return {String} a string representation of the instance. **/ - p._frames = null; + p.toString = function() { + return "[SpriteSheet]"; + }; /** - * @property _images - * @protected + * SpriteSheet cannot be cloned. A SpriteSheet can be shared by multiple Sprite instances without cloning it. + * @method clone **/ - p._images = null; + p.clone = function() { + throw("SpriteSheet cannot be cloned.") + }; +// private methods: /** - * @property _data - * @protected - **/ - p._data = null; - - /** - * @property _loadCount - * @protected - **/ - p._loadCount = 0; - - // only used for simple frame defs: - /** - * @property _frameHeight - * @protected - **/ - p._frameHeight = 0; - - /** - * @property _frameWidth - * @protected - **/ - p._frameWidth = 0; - - /** - * @property _numFrames - * @protected - **/ - p._numFrames = 0; - - /** - * @property _regX - * @protected - **/ - p._regX = 0; - - /** - * @property _regY - * @protected - **/ - p._regY = 0; - -// constructor: - /** - * @method initialize + * @method _parseData * @param {Object} data An object describing the SpriteSheet data. * @protected **/ - p.initialize = function(data) { + p._parseData = function(data) { var i,l,o,a; if (data == null) { return; } @@ -3218,114 +3126,8 @@ var p = SpriteSheet.prototype = new createjs.EventDispatcher(); this._data[name] = anim; } } - }; -// public methods: - /** - * Returns the total number of frames in the specified animation, or in the whole sprite - * sheet if the animation param is omitted. - * @method getNumFrames - * @param {String} animation The name of the animation to get a frame count for. - * @return {Number} The number of frames in the animation, or in the entire sprite sheet if the animation param is omitted. - */ - p.getNumFrames = function(animation) { - if (animation == null) { - return this._frames ? this._frames.length : this._numFrames; - } else { - var data = this._data[animation]; - if (data == null) { return 0; } - else { return data.frames.length; } - } - }; - - /** - * Returns an array of all available animation names as strings. - * @method getAnimations - * @return {Array} an array of animation names available on this sprite sheet. - **/ - p.getAnimations = function() { - return this._animations.slice(0); - }; - - /** - * Returns an object defining the specified animation. The returned object contains: - * @method getAnimation - * @param {String} name The name of the animation to get. - * @return {Object} a generic object with frames, speed, name, and next properties. - **/ - p.getAnimation = function(name) { - return this._data[name]; - }; - - /** - * Returns an object specifying the image and source rect of the specified frame. The returned object has: - * @method getFrame - * @param {Number} frameIndex The index of the frame. - * @return {Object} a generic object with image and rect properties. Returns null if the frame does not exist. - **/ - p.getFrame = function(frameIndex) { - var frame; - if (this._frames && (frame=this._frames[frameIndex])) { return frame; } - return null; - }; - - /** - * Returns a {{#crossLink "Rectangle"}}{{/crossLink}} instance defining the bounds of the specified frame relative - * to the origin. For example, a 90 x 70 frame with a regX of 50 and a regY of 40 would return: - * - * [x=-50, y=-40, width=90, height=70] - * - * @method getFrameBounds - * @param {Number} frameIndex The index of the frame. - * @param {Rectangle} [rectangle] A Rectangle instance to copy the values into. By default a new instance is created. - * @return {Rectangle} A Rectangle instance. Returns null if the frame does not exist, or the image is not fully loaded. - **/ - p.getFrameBounds = function(frameIndex, rectangle) { - var frame = this.getFrame(frameIndex); - return frame ? (rectangle||new createjs.Rectangle()).initialize(-frame.regX, -frame.regY, frame.rect.width, frame.rect.height) : null; - }; - - /** - * Returns a string representation of this object. - * @method toString - * @return {String} a string representation of the instance. - **/ - p.toString = function() { - return "[SpriteSheet]"; - }; - - /** - * Returns a clone of the SpriteSheet instance. - * @method clone - * @return {SpriteSheet} a clone of the SpriteSheet instance. - **/ - p.clone = function() { - // TODO: there isn't really any reason to clone SpriteSheet instances, because they can be reused. - var o = new SpriteSheet(); - o.complete = this.complete; - o._animations = this._animations; - o._frames = this._frames; - o._images = this._images; - o._data = this._data; - o._frameHeight = this._frameHeight; - o._frameWidth = this._frameWidth; - o._numFrames = this._numFrames; - o._loadCount = this._loadCount; - return o; - }; - -// private methods: /** * @method _handleImageLoad * @protected @@ -3361,143 +3163,210 @@ var p = SpriteSheet.prototype = new createjs.EventDispatcher(); this._numFrames = ttlFrames; }; -createjs.SpriteSheet = SpriteSheet; + + createjs.SpriteSheet = createjs.promote(SpriteSheet, "EventDispatcher"); }()); -/* -* Graphics -* Visit http://createjs.com/ for documentation, updates and examples. -* -* Copyright (c) 2010 gskinner.com, inc. -* -* Permission is hereby granted, free of charge, to any person -* obtaining a copy of this software and associated documentation -* files (the "Software"), to deal in the Software without -* restriction, including without limitation the rights to use, -* copy, modify, merge, publish, distribute, sublicense, and/or sell -* copies of the Software, and to permit persons to whom the -* Software is furnished to do so, subject to the following -* conditions: -* -* The above copyright notice and this permission notice shall be -* included in all copies or substantial portions of the Software. -* -* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES -* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT -* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR -* OTHER DEALINGS IN THE SOFTWARE. -*/ -/** - * @module EaselJS - */ +//############################################################################## +// Graphics.js +//############################################################################## -// namespace: this.createjs = this.createjs||{}; (function() { "use strict"; -/** -* Inner class used by the {{#crossLink "Graphics"}}{{/crossLink}} class. Used to create the instruction lists used in Graphics: -* @class Command -* @protected -* @constructor -**/ -function Command(f, params, path) { - this.f = f; - this.params = params; - this.path = path==null ? true : path; -} -/** -* @method exec -* @protected -* @param {Object} scope -**/ -Command.prototype.exec = function(scope) { this.f.apply(scope, this.params); }; +// constructor: + /** + * The Graphics class exposes an easy to use API for generating vector drawing instructions and drawing them to a + * specified context. Note that you can use Graphics without any dependency on the Easel framework by calling {{#crossLink "Graphics/draw"}}{{/crossLink}} + * directly, or it can be used with the {{#crossLink "Shape"}}{{/crossLink}} object to draw vector graphics within the + * context of an EaselJS display list. + * + * There are two approaches to working with Graphics object: calling methods on a Graphics instance (the "Graphics API"), or + * instantiating Graphics command objects and adding them to the graphics queue via {{#crossLink "Graphics/append"}}{{/crossLink}}. + * The former abstracts the latter, simplifying beginning and ending paths, fills, and strokes. + * + * var g = new createjs.Graphics(); + * g.setStrokeStyle(1); + * g.beginStroke("#000000"); + * g.beginFill("red"); + * g.drawCircle(0,0,30); + * + * All drawing methods in Graphics return the Graphics instance, so they can be chained together. For example, + * the following line of code would generate the instructions to draw a rectangle with a red stroke and blue fill: + * + * myGraphics.beginStroke("red").beginFill("blue").drawRect(20, 20, 100, 50); + * + * Each graphics API call generates a command object (see below). The last command to be created can be accessed via + * {{#crossLink "Graphics/command:property"}}{{/crossLink}}: + * + * var fillCommand = myGraphics.beginFill("red").command; + * // ... later, update the fill style/color: + * fillCommand.style = "blue"; + * // or change it to a bitmap fill: + * fillCommand.bitmap(myImage); + * + * For more direct control of rendering, you can instantiate and append command objects to the graphics queue directly. In this case, you + * need to manage path creation manually, and ensure that fill/stroke is applied to a defined path: + * + * // start a new path. Graphics.beginPath is a reusable BeginPath instance: + * myGraphics.append(Graphics.beginPath); + * // we need to define the path before applying the fill: + * var circle = new Graphics.Circle(0,0,30); + * myGraphics.append(circle); + * // fill the path we just defined: + * var fill = new Graphics.Fill("red"); + * myGraphics.append(fill); + * + * These approaches can be used together, for example to insert a custom command: + * + * myGraphics.beginFill("red"); + * var customCommand = new CustomSpiralCommand(etc); + * myGraphics.append(customCommand); + * myGraphics.beginFill("blue"); + * myGraphics.drawCircle(0, 0, 30); + * + * See {{#crossLink "Graphics/append"}}{{/crossLink}} for more info on creating custom commands. + * + *

Tiny API

+ * The Graphics class also includes a "tiny API", which is one or two-letter methods that are shortcuts for all of the + * Graphics methods. These methods are great for creating compact instructions, and is used by the Toolkit for CreateJS + * to generate readable code. All tiny methods are marked as protected, so you can view them by enabling protected + * descriptions in the docs. + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
TinyMethodTinyMethod
mt{{#crossLink "Graphics/moveTo"}}{{/crossLink}} lt {{#crossLink "Graphics/lineTo"}}{{/crossLink}}
a/at{{#crossLink "Graphics/arc"}}{{/crossLink}} / {{#crossLink "Graphics/arcTo"}}{{/crossLink}} bt{{#crossLink "Graphics/bezierCurveTo"}}{{/crossLink}}
qt{{#crossLink "Graphics/quadraticCurveTo"}}{{/crossLink}} (also curveTo)r{{#crossLink "Graphics/rect"}}{{/crossLink}}
cp{{#crossLink "Graphics/closePath"}}{{/crossLink}} c{{#crossLink "Graphics/clear"}}{{/crossLink}}
f{{#crossLink "Graphics/beginFill"}}{{/crossLink}} lf{{#crossLink "Graphics/beginLinearGradientFill"}}{{/crossLink}}
rf{{#crossLink "Graphics/beginRadialGradientFill"}}{{/crossLink}} bf{{#crossLink "Graphics/beginBitmapFill"}}{{/crossLink}}
ef{{#crossLink "Graphics/endFill"}}{{/crossLink}} ss{{#crossLink "Graphics/setStrokeStyle"}}{{/crossLink}}
s{{#crossLink "Graphics/beginStroke"}}{{/crossLink}} ls{{#crossLink "Graphics/beginLinearGradientStroke"}}{{/crossLink}}
rs{{#crossLink "Graphics/beginRadialGradientStroke"}}{{/crossLink}} bs{{#crossLink "Graphics/beginBitmapStroke"}}{{/crossLink}}
es{{#crossLink "Graphics/endStroke"}}{{/crossLink}} dr{{#crossLink "Graphics/drawRect"}}{{/crossLink}}
rr{{#crossLink "Graphics/drawRoundRect"}}{{/crossLink}} rc{{#crossLink "Graphics/drawRoundRectComplex"}}{{/crossLink}}
dc{{#crossLink "Graphics/drawCircle"}}{{/crossLink}} de{{#crossLink "Graphics/drawEllipse"}}{{/crossLink}}
dp{{#crossLink "Graphics/drawPolyStar"}}{{/crossLink}} p{{#crossLink "Graphics/decodePath"}}{{/crossLink}}
+ * + * Here is the above example, using the tiny API instead. + * + * myGraphics.s("red").f("blue").r(20, 20, 100, 50); + * + * @class Graphics + * @constructor + **/ + function Graphics() { + + + // public properties + /** + * Holds a reference to the last command that was created or appended. For example, you could retain a reference + * to a Fill command in order to dynamically update the color later by using: + * myFill = myGraphics.beginFill("red").command; + * // update color later: + * myFill.style = "yellow"; + * @property command + * @type Object + **/ + this.command = null; + + + // private properties + /** + * @property _stroke + * @protected + * @type {Array} + **/ + this._stroke = null; + + /** + * @property _strokeStyle + * @protected + * @type {Array} + **/ + this._strokeStyle = null; + + /** + * @property _strokeIgnoreScale + * @protected + * @type Boolean + **/ + this._strokeIgnoreScale = false; + + /** + * @property _fill + * @protected + * @type {Array} + **/ + this._fill = null; + + /** + * @property _instructions + * @protected + * @type {Array} + **/ + this._instructions = []; + + /** + * Indicates the last instruction index that was committed. + * @property _commitIndex + * @protected + * @type {Number} + **/ + this._commitIndex = 0; + + /** + * Uncommitted instructions. + * @property _activeInstructions + * @protected + * @type {Array} + **/ + this._activeInstructions = []; + + /** + * This indicates that there have been changes to the activeInstruction list since the last updateInstructions call. + * @property _dirty + * @protected + * @type {Boolean} + * @default false + **/ + this._dirty = false; + + /** + * Index to draw from if a store operation has happened. + * @property _storeIndex + * @protected + * @type {Number} + * @default 0 + **/ + this._storeIndex = 0; + + // setup: + this.clear(); + } + var p = Graphics.prototype; + var G = Graphics; // shortcut -/** - * The Graphics class exposes an easy to use API for generating vector drawing instructions and drawing them to a - * specified context. Note that you can use Graphics without any dependency on the Easel framework by calling {{#crossLink "DisplayObject/draw"}}{{/crossLink}} - * directly, or it can be used with the {{#crossLink "Shape"}}{{/crossLink}} object to draw vector graphics within the - * context of an Easel display list. - * - *

Example

- * var g = new createjs.Graphics(); - * g.setStrokeStyle(1); - * g.beginStroke(createjs.Graphics.getRGB(0,0,0)); - * g.beginFill(createjs.Graphics.getRGB(255,0,0)); - * g.drawCircle(0,0,3); - * - * var s = new createjs.Shape(g); - * s.x = 100; - * s.y = 100; - * - * stage.addChild(s); - * stage.update(); - * - * Note that all drawing methods in Graphics return the Graphics instance, so they can be chained together. For example, - * the following line of code would generate the instructions to draw a rectangle with a red stroke and blue fill, then - * render it to the specified context2D: - * - * myGraphics.beginStroke("#F00").beginFill("#00F").drawRect(20, 20, 100, 50).draw(myContext2D); - * - *

Tiny API

- * The Graphics class also includes a "tiny API", which is one or two-letter methods that are shortcuts for all of the - * Graphics methods. These methods are great for creating compact instructions, and is used by the Toolkit for CreateJS - * to generate readable code. All tiny methods are marked as protected, so you can view them by enabling protected - * descriptions in the docs. - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - *
TinyMethodTinyMethod
mt{{#crossLink "Graphics/moveTo"}}{{/crossLink}} lt {{#crossLink "Graphics/lineTo"}}{{/crossLink}}
a/at{{#crossLink "Graphics/arc"}}{{/crossLink}} / {{#crossLink "Graphics/arcTo"}}{{/crossLink}} bt{{#crossLink "Graphics/bezierCurveTo"}}{{/crossLink}}
qt{{#crossLink "Graphics/quadraticCurveTo"}}{{/crossLink}} (also curveTo)r{{#crossLink "Graphics/rect"}}{{/crossLink}}
cp{{#crossLink "Graphics/closePath"}}{{/crossLink}} c{{#crossLink "Graphics/clear"}}{{/crossLink}}
f{{#crossLink "Graphics/beginFill"}}{{/crossLink}} lf{{#crossLink "Graphics/beginLinearGradientFill"}}{{/crossLink}}
rf{{#crossLink "Graphics/beginRadialGradientFill"}}{{/crossLink}} bf{{#crossLink "Graphics/beginBitmapFill"}}{{/crossLink}}
ef{{#crossLink "Graphics/endFill"}}{{/crossLink}} ss{{#crossLink "Graphics/setStrokeStyle"}}{{/crossLink}}
s{{#crossLink "Graphics/beginStroke"}}{{/crossLink}} ls{{#crossLink "Graphics/beginLinearGradientStroke"}}{{/crossLink}}
rs{{#crossLink "Graphics/beginRadialGradientStroke"}}{{/crossLink}} bs{{#crossLink "Graphics/beginBitmapStroke"}}{{/crossLink}}
es{{#crossLink "Graphics/endStroke"}}{{/crossLink}} dr{{#crossLink "Graphics/drawRect"}}{{/crossLink}}
rr{{#crossLink "Graphics/drawRoundRect"}}{{/crossLink}} rc{{#crossLink "Graphics/drawRoundRectComplex"}}{{/crossLink}}
dc{{#crossLink "Graphics/drawCircle"}}{{/crossLink}} de{{#crossLink "Graphics/drawEllipse"}}{{/crossLink}}
dp{{#crossLink "Graphics/drawPolyStar"}}{{/crossLink}} p{{#crossLink "Graphics/decodePath"}}{{/crossLink}}
- * - * Here is the above example, using the tiny API instead. - * - * myGraphics.s("#F00").f("#00F").r(20, 20, 100, 50).draw(myContext2D); - * - * @class Graphics - * @constructor - * @for Graphics - **/ -var Graphics = function() { - this.initialize(); -}; -var p = Graphics.prototype; // static public methods: - - /** * Returns a CSS compatible color string based on the specified RGB numeric color values in the format * "rgba(255,255,255,1.0)", or if alpha is null then in the format "rgb(255,255,255)". For example, @@ -3558,16 +3427,16 @@ var p = Graphics.prototype; } }; -// static properties: +// static properties: /** - * Exposes the Command class used internally by Graphics. Useful for extending the Graphics class or injecting - * functionality. - * @property Command + * A reusable instance of {{#crossLink "Graphics/BeginPath"}}{{/crossLink}} to avoid + * unnecessary instantiation. + * @property beginCmd + * @type {Graphics.BeginPath} * @static - * @type {Function} **/ - Graphics.Command = Command; + // defined at the bottom of this file. /** * Map of Base64 characters to values. Used by {{#crossLink "Graphics/decodePath"}}{{/crossLink}}. @@ -3618,128 +3487,21 @@ var p = Graphics.prototype; * @protected * @type {CanvasRenderingContext2D} **/ - - /** - * @property beginCmd - * @static - * @protected - * @type {Command} - **/ - - /** - * @property fillCmd - * @static - * @protected - * @type {Command} - **/ - - /** - * @property strokeCmd - * @static - * @protected - * @type {Command} - **/ var canvas = (createjs.createCanvas?createjs.createCanvas():document.createElement("canvas")); if (canvas.getContext) { - var ctx = Graphics._ctx = canvas.getContext("2d"); - Graphics.beginCmd = new Command(ctx.beginPath, [], false); - Graphics.fillCmd = new Command(ctx.fill, [], false); - Graphics.strokeCmd = new Command(ctx.stroke, [], false); + Graphics._ctx = canvas.getContext("2d"); canvas.width = canvas.height = 1; } -// public properties - -// private properties - /** - * @property _strokeInstructions - * @protected - * @type {Array} - **/ - p._strokeInstructions = null; - - /** - * @property _strokeStyleInstructions - * @protected - * @type {Array} - **/ - p._strokeStyleInstructions = null; - - /** - * @property _strokeIgnoreScale - * @protected - * @type Boolean - **/ - p._strokeIgnoreScale = false; - - /** - * @property _fillInstructions - * @protected - * @type {Array} - **/ - p._fillInstructions = null; - - /** - * @property _strokeMatrix - * @protected - * @type {Array} - **/ - p._fillMatrix = null; - - /** - * @property _instructions - * @protected - * @type {Array} - **/ - p._instructions = null; - - /** - * @property _oldInstructions - * @protected - * @type {Array} - **/ - p._oldInstructions = null; - - /** - * @property _activeInstructions - * @protected - * @type {Array} - **/ - p._activeInstructions = null; - - /** - * @property _active - * @protected - * @type {Boolean} - * @default false - **/ - p._active = false; - - /** - * @property _dirty - * @protected - * @type {Boolean} - * @default false - **/ - p._dirty = false; - - /** - * Initialization method. - * @method initialize - * @protected - **/ - p.initialize = function() { - this.clear(); - this._ctx = Graphics._ctx; - }; - + +// public methods: /** * Returns true if this Graphics instance has no drawing commands. * @method isEmpty * @return {Boolean} Returns true if this Graphics instance has no drawing commands. **/ p.isEmpty = function() { - return !(this._instructions.length || this._oldInstructions.length || this._activeInstructions.length); + return !(this._instructions.length || this._activeInstructions.length); }; /** @@ -3749,30 +3511,32 @@ var p = Graphics.prototype; * NOTE: This method is mainly for internal use, though it may be useful for advanced uses. * @method draw * @param {CanvasRenderingContext2D} ctx The canvas 2D context object to draw into. + * @param {Object} data Optional data that is passed to graphics command exec methods. When called from a Shape instance, the shape passes itself as the data parameter. This can be used by custom graphic commands to insert contextual data. **/ - p.draw = function(ctx) { - if (this._dirty) { this._updateInstructions(); } + p.draw = function(ctx, data) { + this._updateInstructions(); var instr = this._instructions; - for (var i=0, l=instr.length; iDisplayObject.clippingPath to draw the clipping path, for example. + * stroke descriptions. Used for DisplayObject.mask to draw the clipping path, for example. * @method drawAsPath * @param {CanvasRenderingContext2D} ctx The canvas 2D context object to draw into. **/ p.drawAsPath = function(ctx) { - if (this._dirty) { this._updateInstructions(); } + this._updateInstructions(); var instr, instrs = this._instructions; for (var i=0, l=instrs.length; i max) { radiusTL = max; } - if (radiusTR < 0) { radiusTR *= (mTR=-1); } - if (radiusTR > max) { radiusTR = max; } - if (radiusBR < 0) { radiusBR *= (mBR=-1); } - if (radiusBR > max) { radiusBR = max; } - if (radiusBL < 0) { radiusBL *= (mBL=-1); } - if (radiusBL > max) { radiusBL = max; } - - this._dirty = this._active = true; - var arcTo=this._ctx.arcTo, lineTo=this._ctx.lineTo; - this._activeInstructions.push( - new Command(this._ctx.moveTo, [x+w-radiusTR, y]), - new Command(arcTo, [x+w+radiusTR*mTR, y-radiusTR*mTR, x+w, y+radiusTR, radiusTR]), - new Command(lineTo, [x+w, y+h-radiusBR]), - new Command(arcTo, [x+w+radiusBR*mBR, y+h+radiusBR*mBR, x+w-radiusBR, y+h, radiusBR]), - new Command(lineTo, [x+radiusBL, y+h]), - new Command(arcTo, [x-radiusBL*mBL, y+h+radiusBL*mBL, x, y+h-radiusBL, radiusBL]), - new Command(lineTo, [x, y+radiusTL]), - new Command(arcTo, [x-radiusTL*mTL, y-radiusTL*mTL, x+radiusTL, y, radiusTL]), - new Command(this._ctx.closePath) - ); - return this; + return this.append(new G.RoundRect(x, y, w, h, radiusTL, radiusTR, radiusBR, radiusBL)); }; /** @@ -4278,8 +3953,7 @@ var p = Graphics.prototype; * @return {Graphics} The Graphics instance the method is called on (useful for chaining calls.) **/ p.drawCircle = function(x, y, radius) { - this.arc(x, y, radius, 0, Math.PI*2); - return this; + return this.append(new G.Circle(x, y, radius)); }; /** @@ -4296,70 +3970,7 @@ var p = Graphics.prototype; * @return {Graphics} The Graphics instance the method is called on (useful for chaining calls.) **/ p.drawEllipse = function(x, y, w, h) { - this._dirty = this._active = true; - var k = 0.5522848; - var ox = (w / 2) * k; - var oy = (h / 2) * k; - var xe = x + w; - var ye = y + h; - var xm = x + w / 2; - var ym = y + h / 2; - - this._activeInstructions.push( - new Command(this._ctx.moveTo, [x, ym]), - new Command(this._ctx.bezierCurveTo, [x, ym-oy, xm-ox, y, xm, y]), - new Command(this._ctx.bezierCurveTo, [xm+ox, y, xe, ym-oy, xe, ym]), - new Command(this._ctx.bezierCurveTo, [xe, ym+oy, xm+ox, ye, xm, ye]), - new Command(this._ctx.bezierCurveTo, [xm-ox, ye, x, ym+oy, x, ym]) - ); - return this; - }; - - /** - * Provides a method for injecting arbitrary Context2D (aka Canvas) API calls into a Graphics queue. The specified - * callback function will be called in sequence with other drawing instructions. The callback will be executed in the - * scope of the target canvas's Context2D object, and will be passed the data object as a parameter. - * - * This is an advanced feature. It can allow for powerful functionality, like injecting output from tools that - * export Context2D instructions, executing raw canvas calls within the context of the display list, or dynamically - * modifying colors or stroke styles within a Graphics instance over time, but it is not intended for general use. - * - * Within a Graphics queue, each path begins by applying the fill and stroke styles and settings, followed by - * drawing instructions, followed by the fill() and/or stroke() commands. This means that within a path, inject() can - * update the fill & stroke styles, but for it to be applied in a predictable manner, you must have begun a fill or - * stroke (as appropriate) normally via the Graphics API. For example: - * - * function setColor(color) { - * this.fillStyle = color; - * } - * - * // this will not draw anything - no fill was begun, so fill() is not called: - * myGraphics.inject(setColor, "red").drawRect(0,0,100,100); - * - * // this will draw the rect in green: - * myGraphics.beginFill("#000").inject(setColor, "green").drawRect(0,0,100,100); - * - * // this will draw both rects in blue, because there is only a single path - * // so the second inject overwrites the first: - * myGraphics.beginFill("#000").inject(setColor, "green").drawRect(0,0,100,100) - * .inject(setColor, "blue").drawRect(100,0,100,100); - * - * // this will draw the first rect in green, and the second in blue: - * myGraphics.beginFill("#000").inject(setColor, "green").drawRect(0,0,100,100) - * .beginFill("#000").inject(setColor, "blue").drawRect(100,0,100,100); - * - * @method inject - * @param {Function} callback The function to execute. - * @param {Object} data Arbitrary data that will be passed to the callback when it is executed. - * @return {Graphics} The Graphics instance the method is called on (useful for chaining calls.) - **/ - p.inject = function(callback, data) { - this._dirty = this._active = true; - - this._activeInstructions.push( - new Command(callback, [data]) - ); - return this; + return this.append(new G.Ellipse(x, y, w, h)); }; /** @@ -4384,22 +3995,45 @@ var p = Graphics.prototype; * @return {Graphics} The Graphics instance the method is called on (useful for chaining calls.) **/ p.drawPolyStar = function(x, y, radius, sides, pointSize, angle) { - this._dirty = this._active = true; - if (pointSize == null) { pointSize = 0; } - pointSize = 1-pointSize; - if (angle == null) { angle = 0; } - else { angle /= 180/Math.PI; } - var a = Math.PI/sides; + return this.append(new G.PolyStar(x, y, radius, sides, pointSize, angle)); + }; - this._activeInstructions.push(new Command(this._ctx.moveTo, [x+Math.cos(angle)*radius, y+Math.sin(angle)*radius])); - for (var i=0; i max) { rTL = max; } + if (rTR < 0) { rTR *= (mTR=-1); } + if (rTR > max) { rTR = max; } + if (rBR < 0) { rBR *= (mBR=-1); } + if (rBR > max) { rBR = max; } + if (rBL < 0) { rBL *= (mBL=-1); } + if (rBL > max) { rBL = max; } -createjs.Graphics = Graphics; + ctx.moveTo(x+w-rTR, y); + ctx.arcTo(x+w+rTR*mTR, y-rTR*mTR, x+w, y+rTR, rTR); + ctx.lineTo(x+w, y+h-rBR); + ctx.arcTo(x+w+rBR*mBR, y+h+rBR*mBR, x+w-rBR, y+h, rBR); + ctx.lineTo(x+rBL, y+h); + ctx.arcTo(x-rBL*mBL, y+h+rBL*mBL, x, y+h-rBL, rBL); + ctx.lineTo(x, y+rTL); + ctx.arcTo(x-rTL*mTL, y-rTL*mTL, x+rTL, y, rTL); + ctx.closePath(); + }; + + /** + * Graphics command object. See {{#crossLink "Graphics"}}{{/crossLink}} and {{#crossLink "Graphics/append"}}{{/crossLink}} for more information. + * @class Circle + * @constructor + * @param {Number} x + * @param {Number} y + * @param {Number} radius + **/ + /** + * @property x + * @type Number + */ + /** + * @property y + * @type Number + */ + /** + * @property radius + * @type Number + */ + (G.Circle = function(x, y, radius) { + this.x = x; this.y = y; + this.radius = radius; + }).prototype.exec = function(ctx) { ctx.arc(this.x, this.y, this.radius, 0, Math.PI*2); }; + + (G.Ellipse = function(x, y, w, h) { + this.x = x; this.y = y; + this.w = w; this.h = h; + }).prototype.exec = function(ctx) { + var x = this.x, y = this.y; + var w = this.w, h = this.h; + + var k = 0.5522848; + var ox = (w / 2) * k; + var oy = (h / 2) * k; + var xe = x + w; + var ye = y + h; + var xm = x + w / 2; + var ym = y + h / 2; + + ctx.moveTo(x, ym); + ctx.bezierCurveTo(x, ym-oy, xm-ox, y, xm, y); + ctx.bezierCurveTo(xm+ox, y, xe, ym-oy, xe, ym); + ctx.bezierCurveTo(xe, ym+oy, xm+ox, ye, xm, ye); + ctx.bezierCurveTo(xm-ox, ye, x, ym+oy, x, ym); + }; + + /** + * Graphics command object. See {{#crossLink "Graphics"}}{{/crossLink}} and {{#crossLink "Graphics/append"}}{{/crossLink}} for more information. + * @class PolyStar + * @constructor + * @param {Number} x + * @param {Number} y + * @param {Number} radius + * @param {Number} sides + * @param {Number} pointSize + * @param {Number} angle + **/ + /** + * @property x + * @type Number + */ + /** + * @property y + * @type Number + */ + /** + * @property radius + * @type Number + */ + /** + * @property sides + * @type Number + */ + /** + * @property pointSize + * @type Number + */ + /** + * @property angle + * @type Number + */ + (G.PolyStar = function(x, y, radius, sides, pointSize, angle) { + this.x = x; this.y = y; + this.radius = radius; + this.sides = sides; + this.pointSize = pointSize; + this.angle = angle; + }).prototype.exec = function(ctx) { + var x = this.x, y = this.y; + var radius = this.radius; + var angle = (this.angle||0)/180*Math.PI; + var sides = this.sides; + var ps = 1-(this.pointSize||0); + var a = Math.PI/sides; + + ctx.moveTo(x+Math.cos(angle)*radius, y+Math.sin(angle)*radius); + for (var i=0; iGetting Started - * To get started with Easel, create a {{#crossLink "Stage"}}{{/crossLink}} that wraps a CANVAS element, and add - * {{#crossLink "DisplayObject"}}{{/crossLink}} instances as children. EaselJS supports: - *
    - *
  • Images using {{#crossLink "Bitmap"}}{{/crossLink}}
  • - *
  • Vector graphics using {{#crossLink "Shape"}}{{/crossLink}} and {{#crossLink "Graphics"}}{{/crossLink}}
  • - *
  • Animated bitmaps using {{#crossLink "SpriteSheet"}}{{/crossLink}} and {{#crossLink "Sprite"}}{{/crossLink}} - *
  • Simple text instances using {{#crossLink "Text"}}{{/crossLink}}
  • - *
  • Containers that hold other DisplayObjects using {{#crossLink "Container"}}{{/crossLink}}
  • - *
  • Control HTML DOM elements using {{#crossLink "DOMElement"}}{{/crossLink}}
  • - *
- * - * All display objects can be added to the stage as children, or drawn to a canvas directly. - * - * User Interactions
- * All display objects on stage (except DOMElement) will dispatch events when interacted with using a mouse or - * touch. EaselJS supports hover, press, and release events, as well as an easy-to-use drag-and-drop model. Check out - * {{#crossLink "MouseEvent"}}{{/crossLink}} for more information. - * - *

Simple Example

- * This example illustrates how to create and position a {{#crossLink "Shape"}}{{/crossLink}} on the {{#crossLink "Stage"}}{{/crossLink}} - * using EaselJS' drawing API. - * - * //Create a stage by getting a reference to the canvas - * stage = new createjs.Stage("demoCanvas"); - * //Create a Shape DisplayObject. - * circle = new createjs.Shape(); - * circle.graphics.beginFill("red").drawCircle(0, 0, 40); - * //Set position of Shape instance. - * circle.x = circle.y = 50; - * //Add Shape instance to stage display list. - * stage.addChild(circle); - * //Update stage will render next frame - * stage.update(); - * - * Simple Interaction Example
- * - * displayObject.addEventListener("click", handleClick); - * function handleClick(event){ - * // Click happenened - * } - * - * displayObject.addEventListener("mousedown", handlePress); - * function handlePress(event) { - * // A mouse press happened. - * // Listen for mouse move while the mouse is down: - * event.addEventListener("mousemove", handleMove); - * } - * function handleMove(event) { - * // Check out the DragAndDrop example in GitHub for more - * } - * - * Simple Animation Example
- * This example moves the shape created in the previous demo across the screen. - * - * //Update stage will render next frame - * createjs.Ticker.addEventListener("tick", handleTick); - * - * function handleTick() { - * //Circle will move 10 units to the right. - * circle.x += 10; - * //Will cause the circle to wrap back - * if (circle.x > stage.canvas.width) { circle.x = 0; } - * stage.update(); - * } - * - *

Other Features

- * EaselJS also has built in support for - *
  • Canvas features such as {{#crossLink "Shadow"}}{{/crossLink}} and CompositeOperation
  • - *
  • {{#crossLink "Ticker"}}{{/crossLink}}, a global heartbeat that objects can subscribe to
  • - *
  • Filters, including a provided {{#crossLink "ColorMatrixFilter"}}{{/crossLink}}, {{#crossLink "AlphaMaskFilter"}}{{/crossLink}}, - * {{#crossLink "AlphaMapFilter"}}{{/crossLink}}, and {{#crossLink "BlurFilter"}}{{/crossLink}}. See {{#crossLink "Filter"}}{{/crossLink}} - * for more information
  • - *
  • A {{#crossLink "ButtonHelper"}}{{/crossLink}} utility, to easily create interactive buttons
  • - *
  • {{#crossLink "SpriteSheetUtils"}}{{/crossLink}} and a {{#crossLink "SpriteSheetBuilder"}}{{/crossLink}} to - * help build and manage {{#crossLink "SpriteSheet"}}{{/crossLink}} functionality at run-time.
  • - *
- * - *

Browser Support

- * All modern browsers that support Canvas will support EaselJS (http://caniuse.com/canvas). - * Browser performance may vary between platforms, for example, Android Canvas has poor hardware support, and is much - * slower on average than most other browsers. - * - * @module EaselJS - * @main EaselJS - */ +//############################################################################## +// DisplayObject.js +//############################################################################## -// namespace: this.createjs = this.createjs||{}; (function() { -/** - * DisplayObject is an abstract class that should not be constructed directly. Instead construct subclasses such as - * {{#crossLink "Container"}}{{/crossLink}}, {{#crossLink "Bitmap"}}{{/crossLink}}, and {{#crossLink "Shape"}}{{/crossLink}}. - * DisplayObject is the base class for all display classes in the EaselJS library. It defines the core properties and - * methods that are shared between all display objects, such as transformation properties (x, y, scaleX, scaleY, etc), - * caching, and mouse handlers. - * @class DisplayObject - * @extends EventDispatcher - * @constructor - **/ -var DisplayObject = function() { - this.initialize(); -}; -var p = DisplayObject.prototype = new createjs.EventDispatcher(); + "use strict"; + + +// constructor: + /** + * DisplayObject is an abstract class that should not be constructed directly. Instead construct subclasses such as + * {{#crossLink "Container"}}{{/crossLink}}, {{#crossLink "Bitmap"}}{{/crossLink}}, and {{#crossLink "Shape"}}{{/crossLink}}. + * DisplayObject is the base class for all display classes in the EaselJS library. It defines the core properties and + * methods that are shared between all display objects, such as transformation properties (x, y, scaleX, scaleY, etc), + * caching, and mouse handlers. + * @class DisplayObject + * @extends EventDispatcher + * @constructor + **/ + function DisplayObject() { + this.EventDispatcher_constructor(); + + + // public properties: + /** + * The alpha (transparency) for this display object. 0 is fully transparent, 1 is fully opaque. + * @property alpha + * @type {Number} + * @default 1 + **/ + this.alpha = 1; + + /** + * If a cache is active, this returns the canvas that holds the cached version of this display object. See {{#crossLink "cache"}}{{/crossLink}} + * for more information. + * @property cacheCanvas + * @type {HTMLCanvasElement | Object} + * @default null + * @readonly + **/ + this.cacheCanvas = null; + + /** + * Returns an ID number that uniquely identifies the current cache for this display object. This can be used to + * determine if the cache has changed since a previous check. + * @property cacheID + * @type {Number} + * @default 0 + */ + this.cacheID = 0; + + /** + * Unique ID for this display object. Makes display objects easier for some uses. + * @property id + * @type {Number} + * @default -1 + **/ + this.id = createjs.UID.get(); + + /** + * Indicates whether to include this object when running mouse interactions. Setting this to `false` for children + * of a {{#crossLink "Container"}}{{/crossLink}} will cause events on the Container to not fire when that child is + * clicked. Setting this property to `false` does not prevent the {{#crossLink "Container/getObjectsUnderPoint"}}{{/crossLink}} + * method from returning the child. + * + * Note: In EaselJS 0.7.0, the mouseEnabled property will not work properly with nested Containers. Please + * check out the latest NEXT version in GitHub for an updated version with this issue resolved. The fix will be + * provided in the next release of EaselJS. + * @property mouseEnabled + * @type {Boolean} + * @default true + **/ + this.mouseEnabled = true; + + /** + * If false, the tick will not run on this display object (or its children). This can provide some performance benefits. + * In addition to preventing the "tick" event from being dispatched, it will also prevent tick related updates + * on some display objects (ex. Sprite & MovieClip frame advancing, DOMElement visibility handling). + * @property tickEnabled + * @type Boolean + * @default true + **/ + this.tickEnabled = true; + + /** + * An optional name for this display object. Included in {{#crossLink "DisplayObject/toString"}}{{/crossLink}} . Useful for + * debugging. + * @property name + * @type {String} + * @default null + **/ + this.name = null; + + /** + * A reference to the {{#crossLink "Container"}}{{/crossLink}} or {{#crossLink "Stage"}}{{/crossLink}} object that + * contains this display object, or null if it has not been added + * to one. + * @property parent + * @final + * @type {Container} + * @default null + * @readonly + **/ + this.parent = null; + + /** + * The left offset for this display object's registration point. For example, to make a 100x100px Bitmap rotate + * around its center, you would set regX and {{#crossLink "DisplayObject/regY:property"}}{{/crossLink}} to 50. + * @property regX + * @type {Number} + * @default 0 + **/ + this.regX = 0; + + /** + * The y offset for this display object's registration point. For example, to make a 100x100px Bitmap rotate around + * its center, you would set {{#crossLink "DisplayObject/regX:property"}}{{/crossLink}} and regY to 50. + * @property regY + * @type {Number} + * @default 0 + **/ + this.regY = 0; + + /** + * The rotation in degrees for this display object. + * @property rotation + * @type {Number} + * @default 0 + **/ + this.rotation = 0; + + /** + * The factor to stretch this display object horizontally. For example, setting scaleX to 2 will stretch the display + * object to twice its nominal width. To horizontally flip an object, set the scale to a negative number. + * @property scaleX + * @type {Number} + * @default 1 + **/ + this.scaleX = 1; + + /** + * The factor to stretch this display object vertically. For example, setting scaleY to 0.5 will stretch the display + * object to half its nominal height. To vertically flip an object, set the scale to a negative number. + * @property scaleY + * @type {Number} + * @default 1 + **/ + this.scaleY = 1; + + /** + * The factor to skew this display object horizontally. + * @property skewX + * @type {Number} + * @default 0 + **/ + this.skewX = 0; + + /** + * The factor to skew this display object vertically. + * @property skewY + * @type {Number} + * @default 0 + **/ + this.skewY = 0; + + /** + * A shadow object that defines the shadow to render on this display object. Set to `null` to remove a shadow. If + * null, this property is inherited from the parent container. + * @property shadow + * @type {Shadow} + * @default null + **/ + this.shadow = null; + + /** + * Indicates whether this display object should be rendered to the canvas and included when running the Stage + * {{#crossLink "Stage/getObjectsUnderPoint"}}{{/crossLink}} method. + * @property visible + * @type {Boolean} + * @default true + **/ + this.visible = true; + + /** + * The x (horizontal) position of the display object, relative to its parent. + * @property x + * @type {Number} + * @default 0 + **/ + this.x = 0; + + /** The y (vertical) position of the display object, relative to its parent. + * @property y + * @type {Number} + * @default 0 + **/ + this.y = 0; + + /** + * If set, defines the transformation for this display object, overriding all other transformation properties + * (x, y, rotation, scale, skew). + * @property transformMatrix + * @type {Matrix2D} + * @default null + **/ + this.transformMatrix = null; + + /** + * The composite operation indicates how the pixels of this display object will be composited with the elements + * behind it. If `null`, this property is inherited from the parent container. For more information, read the + * + * whatwg spec on compositing. + * @property compositeOperation + * @type {String} + * @default null + **/ + this.compositeOperation = null; + + /** + * Indicates whether the display object should be drawn to a whole pixel when + * {{#crossLink "Stage/snapToPixelEnabled"}}{{/crossLink}} is true. To enable/disable snapping on whole + * categories of display objects, set this value on the prototype (Ex. Text.prototype.snapToPixel = true). + * @property snapToPixel + * @type {Boolean} + * @default true + **/ + this.snapToPixel = true; + + /** + * An array of Filter objects to apply to this display object. Filters are only applied / updated when {{#crossLink "cache"}}{{/crossLink}} + * or {{#crossLink "updateCache"}}{{/crossLink}} is called on the display object, and only apply to the area that is + * cached. + * @property filters + * @type {Array} + * @default null + **/ + this.filters = null; + + /** + * A Shape instance that defines a vector mask (clipping path) for this display object. The shape's transformation + * will be applied relative to the display object's parent coordinates (as if it were a child of the parent). + * @property mask + * @type {Shape} + * @default null + */ + this.mask = null; + + /** + * A display object that will be tested when checking mouse interactions or testing {{#crossLink "Container/getObjectsUnderPoint"}}{{/crossLink}}. + * The hit area will have its transformation applied relative to this display object's coordinate space (as though + * the hit test object were a child of this display object and relative to its regX/Y). The hitArea will be tested + * using only its own `alpha` value regardless of the alpha value on the target display object, or the target's + * ancestors (parents). + * + * If set on a {{#crossLink "Container"}}{{/crossLink}}, children of the Container will not receive mouse events. + * This is similar to setting {{#crossLink "mouseChildren"}}{{/crossLink}} to false. + * + * Note that hitArea is NOT currently used by the `hitTest()` method, nor is it supported for {{#crossLink "Stage"}}{{/crossLink}}. + * @property hitArea + * @type {DisplayObject} + * @default null + */ + this.hitArea = null; + + /** + * A CSS cursor (ex. "pointer", "help", "text", etc) that will be displayed when the user hovers over this display + * object. You must enable mouseover events using the {{#crossLink "Stage/enableMouseOver"}}{{/crossLink}} method to + * use this property. Setting a non-null cursor on a Container will override the cursor set on its descendants. + * @property cursor + * @type {String} + * @default null + */ + this.cursor = null; + + + // private properties: + /** + * @property _cacheOffsetX + * @protected + * @type {Number} + * @default 0 + **/ + this._cacheOffsetX = 0; + + /** + * @property _cacheOffsetY + * @protected + * @type {Number} + * @default 0 + **/ + this._cacheOffsetY = 0; + + /** + * @property _cacheScale + * @protected + * @type {Number} + * @default 1 + **/ + this._cacheScale = 1; + + /** + * @property _cacheDataURLID + * @protected + * @type {Number} + * @default 0 + */ + this._cacheDataURLID = 0; + + /** + * @property _cacheDataURL + * @protected + * @type {String} + * @default null + */ + this._cacheDataURL = null; + + /** + * @property _props + * @protected + * @type {DisplayObject} + * @default null + **/ + this._props = new createjs.DisplayProps(); + + /** + * @property _rectangle + * @protected + * @type {Rectangle} + * @default null + **/ + this._rectangle = new createjs.Rectangle(); + + /** + * @property _bounds + * @protected + * @type {Rectangle} + * @default null + **/ + this._bounds = null; + } + var p = createjs.extend(DisplayObject, createjs.EventDispatcher); // static properties: @@ -4963,8 +5457,8 @@ var p = DisplayObject.prototype = new createjs.EventDispatcher(); **/ DisplayObject._nextCacheID = 1; -// events: +// events: /** * Dispatched when the user presses their left mouse button over the display object. See the * {{#crossLink "MouseEvent"}}{{/crossLink}} class for a listing of event properties. @@ -5058,6 +5552,16 @@ var p = DisplayObject.prototype = new createjs.EventDispatcher(); * @since 0.7.0 */ + /** + * Dispatched when the display object is added to a parent container. + * @event added + */ + + /** + * Dispatched when the display object is removed from its parent container. + * @event removed + */ + /** * Dispatched on each display object on a stage whenever the stage updates. This occurs immediately before the * rendering (draw) pass. When {{#crossLink "Stage/update"}}{{/crossLink}} is called, first all display objects on @@ -5071,373 +5575,7 @@ var p = DisplayObject.prototype = new createjs.EventDispatcher(); * example if you called stage.update("hello"), then the params would be ["hello"]. * @since 0.6.0 */ - -// public properties: - /** - * The alpha (transparency) for this display object. 0 is fully transparent, 1 is fully opaque. - * @property alpha - * @type {Number} - * @default 1 - **/ - p.alpha = 1; - - /** - * If a cache is active, this returns the canvas that holds the cached version of this display object. See {{#crossLink "cache"}}{{/crossLink}} - * for more information. - * @property cacheCanvas - * @type {HTMLCanvasElement | Object} - * @default null - * @readonly - **/ - p.cacheCanvas = null; - - /** - * Unique ID for this display object. Makes display objects easier for some uses. - * @property id - * @type {Number} - * @default -1 - **/ - p.id = -1; - - /** - * Indicates whether to include this object when running mouse interactions. Setting this to `false` for children - * of a {{#crossLink "Container"}}{{/crossLink}} will cause events on the Container to not fire when that child is - * clicked. Setting this property to `false` does not prevent the {{#crossLink "Container/getObjectsUnderPoint"}}{{/crossLink}} - * method from returning the child. - * - * Note: In EaselJS 0.7.0, the mouseEnabled property will not work properly with nested Containers. Please - * check out the latest NEXT version in GitHub for an updated version with this issue resolved. The fix will be - * provided in the next release of EaselJS. - * @property mouseEnabled - * @type {Boolean} - * @default true - **/ - p.mouseEnabled = true; - /** - * If false, the tick will not run on this display object (or its children). This can provide some performance benefits. - * In addition to preventing the "tick" event from being dispatched, it will also prevent tick related updates - * on some display objects (ex. Sprite & MovieClip frame advancing, DOMElement visibility handling). - * @property tickEnabled - * @type Boolean - * @default true - **/ - p.tickEnabled = true; - - /** - * An optional name for this display object. Included in {{#crossLink "DisplayObject/toString"}}{{/crossLink}} . Useful for - * debugging. - * @property name - * @type {String} - * @default null - **/ - p.name = null; - - /** - * A reference to the {{#crossLink "Container"}}{{/crossLink}} or {{#crossLink "Stage"}}{{/crossLink}} object that - * contains this display object, or null if it has not been added - * to one. - * @property parent - * @final - * @type {Container} - * @default null - * @readonly - **/ - p.parent = null; - - /** - * The left offset for this display object's registration point. For example, to make a 100x100px Bitmap rotate - * around its center, you would set regX and {{#crossLink "DisplayObject/regY:property"}}{{/crossLink}} to 50. - * @property regX - * @type {Number} - * @default 0 - **/ - p.regX = 0; - - /** - * The y offset for this display object's registration point. For example, to make a 100x100px Bitmap rotate around - * its center, you would set {{#crossLink "DisplayObject/regX:property"}}{{/crossLink}} and regY to 50. - * @property regY - * @type {Number} - * @default 0 - **/ - p.regY = 0; - - /** - * The rotation in degrees for this display object. - * @property rotation - * @type {Number} - * @default 0 - **/ - p.rotation = 0; - - /** - * The factor to stretch this display object horizontally. For example, setting scaleX to 2 will stretch the display - * object to twice its nominal width. To horizontally flip an object, set the scale to a negative number. - * @property scaleX - * @type {Number} - * @default 1 - **/ - p.scaleX = 1; - - /** - * The factor to stretch this display object vertically. For example, setting scaleY to 0.5 will stretch the display - * object to half its nominal height. To vertically flip an object, set the scale to a negative number. - * @property scaleY - * @type {Number} - * @default 1 - **/ - p.scaleY = 1; - - /** - * The factor to skew this display object horizontally. - * @property skewX - * @type {Number} - * @default 0 - **/ - p.skewX = 0; - - /** - * The factor to skew this display object vertically. - * @property skewY - * @type {Number} - * @default 0 - **/ - p.skewY = 0; - - /** - * A shadow object that defines the shadow to render on this display object. Set to `null` to remove a shadow. If - * null, this property is inherited from the parent container. - * @property shadow - * @type {Shadow} - * @default null - **/ - p.shadow = null; - - /** - * Indicates whether this display object should be rendered to the canvas and included when running the Stage - * {{#crossLink "Stage/getObjectsUnderPoint"}}{{/crossLink}} method. - * @property visible - * @type {Boolean} - * @default true - **/ - p.visible = true; - - /** - * The x (horizontal) position of the display object, relative to its parent. - * @property x - * @type {Number} - * @default 0 - **/ - p.x = 0; - - /** The y (vertical) position of the display object, relative to its parent. - * @property y - * @type {Number} - * @default 0 - **/ - p.y = 0; - - /** - * The composite operation indicates how the pixels of this display object will be composited with the elements - * behind it. If `null`, this property is inherited from the parent container. For more information, read the - * - * whatwg spec on compositing. - * @property compositeOperation - * @type {String} - * @default null - **/ - p.compositeOperation = null; - - /** - * Indicates whether the display object should be drawn to a whole pixel when - * {{#crossLink "Stage/snapToPixelEnabled"}}{{/crossLink}} is true. To enable/disable snapping on whole - * categories of display objects, set this value on the prototype (Ex. Text.prototype.snapToPixel = true). - * @property snapToPixel - * @type {Boolean} - * @default true - **/ - p.snapToPixel = true; - - // TODO: remove handler docs in future: - /** - * REMOVED. Use {{#crossLink "EventDispatcher/addEventListener"}}{{/crossLink}} and the {{#crossLink "DisplayObject/mousedown:event"}}{{/crossLink}} - * event. - * @property onPress - * @type {Function} - * @deprecated Use addEventListener and the "mousedown" event. - */ - /** - * REMOVED. Use {{#crossLink "EventDispatcher/addEventListener"}}{{/crossLink}} and the {{#crossLink "DisplayObject/click:event"}}{{/crossLink}} - * event. - * @property onClick - * @type {Function} - * @deprecated Use addEventListener and the "click" event. - */ - /** - * REMOVED. Use {{#crossLink "EventDispatcher/addEventListener"}}{{/crossLink}} and the {{#crossLink "DisplayObject/dblclick:event"}}{{/crossLink}} - * event. - * @property onDoubleClick - * @type {Function} - * @deprecated Use addEventListener and the "dblclick" event. - */ - /** - * REMOVED. Use {{#crossLink "EventDispatcher/addEventListener"}}{{/crossLink}} and the {{#crossLink "DisplayObject/mouseover:event"}}{{/crossLink}} - * event. - * @property onMouseOver - * @type {Function} - * @deprecated Use addEventListener and the "mouseover" event. - */ - /** - * REMOVED. Use {{#crossLink "EventDispatcher/addEventListener"}}{{/crossLink}} and the {{#crossLink "DisplayObject/mouseout:event"}}{{/crossLink}} - * event. - * @property onMouseOut - * @type {Function} - * @deprecated Use addEventListener and the "mouseout" event. - */ - /** - * REMOVED. Use {{#crossLink "EventDispatcher/addEventListener"}}{{/crossLink}} and the {{#crossLink "DisplayObject/tick:event"}}{{/crossLink}} - * event. - * @property onTick - * @type {Function} - * @deprecated Use addEventListener and the "tick" event. - */ - - /** - * An array of Filter objects to apply to this display object. Filters are only applied / updated when {{#crossLink "cache"}}{{/crossLink}} - * or {{#crossLink "updateCache"}}{{/crossLink}} is called on the display object, and only apply to the area that is - * cached. - * @property filters - * @type {Array} - * @default null - **/ - p.filters = null; - - /** - * Returns an ID number that uniquely identifies the current cache for this display object. This can be used to - * determine if the cache has changed since a previous check. - * @property cacheID - * @type {Number} - * @default 0 - */ - p.cacheID = 0; - - /** - * A Shape instance that defines a vector mask (clipping path) for this display object. The shape's transformation - * will be applied relative to the display object's parent coordinates (as if it were a child of the parent). - * @property mask - * @type {Shape} - * @default null - */ - p.mask = null; - - /** - * A display object that will be tested when checking mouse interactions or testing {{#crossLink "Container/getObjectsUnderPoint"}}{{/crossLink}}. - * The hit area will have its transformation applied relative to this display object's coordinate space (as though - * the hit test object were a child of this display object and relative to its regX/Y). The hitArea will be tested - * using only its own `alpha` value regardless of the alpha value on the target display object, or the target's - * ancestors (parents). - * - * If set on a {{#crossLink "Container"}}{{/crossLink}}, children of the Container will not receive mouse events. - * This is similar to setting {{#crossLink "mouseChildren"}}{{/crossLink}} to false. - * - * Note that hitArea is NOT currently used by the `hitTest()` method, nor is it supported for {{#crossLink "Stage"}}{{/crossLink}}. - * @property hitArea - * @type {DisplayObject} - * @default null - */ - p.hitArea = null; - - /** - * A CSS cursor (ex. "pointer", "help", "text", etc) that will be displayed when the user hovers over this display - * object. You must enable mouseover events using the {{#crossLink "Stage/enableMouseOver"}}{{/crossLink}} method to - * use this property. Setting a non-null cursor on a Container will override the cursor set on its descendants. - * @property cursor - * @type {String} - * @default null - */ - p.cursor = null; - -// private properties: - - /** - * @property _cacheOffsetX - * @protected - * @type {Number} - * @default 0 - **/ - p._cacheOffsetX = 0; - - /** - * @property _cacheOffsetY - * @protected - * @type {Number} - * @default 0 - **/ - p._cacheOffsetY = 0; - - /** - * @property _cacheScale - * @protected - * @type {Number} - * @default 1 - **/ - p._cacheScale = 1; - - /** - * @property _cacheDataURLID - * @protected - * @type {Number} - * @default 0 - */ - p._cacheDataURLID = 0; - - /** - * @property _cacheDataURL - * @protected - * @type {String} - * @default null - */ - p._cacheDataURL = null; - - /** - * @property _matrix - * @protected - * @type {Matrix2D} - * @default null - **/ - p._matrix = null; - - /** - * @property _rectangle - * @protected - * @type {Rectangle} - * @default null - **/ - p._rectangle = null; - - /** - * @property _bounds - * @protected - * @type {Rectangle} - * @default null - **/ - p._bounds = null; - - -// constructor: - // separated so it can be easily addressed in subclasses: - - /** - * Initialization method. - * @method initialize - * @protected - */ - p.initialize = function() { - this.id = createjs.UID.get(); - this._matrix = new createjs.Matrix2D(); - this._rectangle = new createjs.Rectangle(); - }; // public methods: /** @@ -5482,10 +5620,10 @@ var p = DisplayObject.prototype = new createjs.EventDispatcher(); * @param {CanvasRenderingContext2D} ctx The canvas 2D to update. **/ p.updateContext = function(ctx) { - var mtx, mask=this.mask, o=this; + var o=this, mask=o.mask, mtx= o._props.matrix; if (mask && mask.graphics && !mask.graphics.isEmpty()) { - mtx = mask.getMatrix(mask._matrix); + mask.getMatrix(mtx); ctx.transform(mtx.a, mtx.b, mtx.c, mtx.d, mtx.tx, mtx.ty); mask.graphics.drawAsPath(ctx); @@ -5495,7 +5633,7 @@ var p = DisplayObject.prototype = new createjs.EventDispatcher(); ctx.transform(mtx.a, mtx.b, mtx.c, mtx.d, mtx.tx, mtx.ty); } - mtx = o._matrix.identity().appendTransform(o.x, o.y, o.scaleX, o.scaleY, o.rotation, o.skewX, o.skewY, o.regX, o.regY); + this.getMatrix(mtx); var tx = mtx.tx, ty = mtx.ty; if (DisplayObject._snapToPixelEnabled && o.snapToPixel) { tx = tx + (tx < 0 ? -0.5 : 0.5) | 0; @@ -5662,9 +5800,7 @@ var p = DisplayObject.prototype = new createjs.EventDispatcher(); * on the stage. **/ p.localToGlobal = function(x, y) { - var mtx = this.getConcatenatedMatrix(this._matrix); - if (mtx == null) { return null; } - mtx.append(1, 0, 0, 1, x, y); + var mtx = this.getConcatenatedMatrix(this._props.matrix).prepend(1, 0, 0, 1, x, y); return new createjs.Point(mtx.tx, mtx.ty); }; @@ -5689,10 +5825,7 @@ var p = DisplayObject.prototype = new createjs.EventDispatcher(); * display object's coordinate space. **/ p.globalToLocal = function(x, y) { - var mtx = this.getConcatenatedMatrix(this._matrix); - if (mtx == null) { return null; } - mtx.invert(); - mtx.append(1, 0, 0, 1, x, y); + var mtx = this.getConcatenatedMatrix(this._props.matrix).invert().prepend(1, 0, 0, 1, x, y); return new createjs.Point(mtx.tx, mtx.ty); }; @@ -5751,43 +5884,59 @@ var p = DisplayObject.prototype = new createjs.EventDispatcher(); }; /** - * Returns a matrix based on this object's transform. + * Returns a matrix based on this object's current transform. * @method getMatrix * @param {Matrix2D} matrix Optional. A Matrix2D object to populate with the calculated values. If null, a new * Matrix object is returned. * @return {Matrix2D} A matrix representing this display object's transform. **/ p.getMatrix = function(matrix) { - var o = this; - return (matrix ? matrix.identity() : new createjs.Matrix2D()).appendTransform(o.x, o.y, o.scaleX, o.scaleY, o.rotation, o.skewX, o.skewY, o.regX, o.regY).appendProperties(o.alpha, o.shadow, o.compositeOperation); + var o = this, mtx = matrix&&matrix.identity() || new createjs.Matrix2D(); + return o.transformMatrix ? mtx.copy(o.transformMatrix) : mtx.prependTransform(o.x, o.y, o.scaleX, o.scaleY, o.rotation, o.skewX, o.skewY, o.regX, o.regY); }; /** - * Generates a concatenated Matrix2D object representing the combined transform of the display object and all of its + * Generates a Matrix2D object representing the combined transform of the display object and all of its * parent Containers up to the highest level ancestor (usually the {{#crossLink "Stage"}}{{/crossLink}}). This can * be used to transform positions between coordinate spaces, such as with {{#crossLink "DisplayObject/localToGlobal"}}{{/crossLink}} * and {{#crossLink "DisplayObject/globalToLocal"}}{{/crossLink}}. * @method getConcatenatedMatrix * @param {Matrix2D} [matrix] A {{#crossLink "Matrix2D"}}{{/crossLink}} object to populate with the calculated values. * If null, a new Matrix2D object is returned. - * @return {Matrix2D} a concatenated Matrix2D object representing the combined transform of the display object and - * all of its parent Containers up to the highest level ancestor (usually the {{#crossLink "Stage"}}{{/crossLink}}). + * @return {Matrix2D} The combined matrix. **/ p.getConcatenatedMatrix = function(matrix) { - if (matrix) { matrix.identity(); } - else { matrix = new createjs.Matrix2D(); } - var o = this; - while (o != null) { - matrix.prependTransform(o.x, o.y, o.scaleX, o.scaleY, o.rotation, o.skewX, o.skewY, o.regX, o.regY).prependProperties(o.alpha, o.shadow, o.compositeOperation, o.visible); - o = o.parent; + var o = this, mtx = this.getMatrix(matrix); + while (o = o.parent) { + mtx.appendMatrix(o.getMatrix(o._props.matrix)); } - return matrix; + return mtx; + }; + + /** + * Generates a DisplayProps object representing the combined display properties of the object and all of its + * parent Containers up to the highest level ancestor (usually the {{#crossLink "Stage"}}{{/crossLink}}). + * @method getConcatenatedDisplayProps + * @param {DisplayProps} [props] A {{#crossLink "DisplayProps"}}{{/crossLink}} object to populate with the calculated values. + * If null, a new DisplayProps object is returned. + * @return {DisplayProps} The combined display properties. + **/ + p.getConcatenatedDisplayProps = function(props) { + props = props ? props.identity() : new createjs.DisplayProps(); + var o = this, mtx = o.getMatrix(props.matrix); + do { + props.append(o.visible, o.alpha, o.shadow, o.compositeOperation); + + // we do this to avoid problems with the matrix being used for both operations when o._props.matrix is passed in as the props param. + // this could be simplified (ie. just done as part of the append above) if we switched to using a pool. + if (o != this) { mtx.appendMatrix(o.getMatrix(o._props.matrix)); } + } while (o = o.parent); + return props; }; /** - * Tests whether the display object intersects the specified local point (ie. draws a pixel with alpha > 0 at - * the specified position). This ignores the alpha, shadow and compositeOperation of the display object, and all - * transform properties including regX/Y. + * Tests whether the display object intersects the specified point in local coordinates (ie. draws a pixel with alpha > 0 at + * the specified position). This ignores the alpha, shadow, hitArea, mask, and compositeOperation of the display object. * *

Example

* @@ -5804,7 +5953,6 @@ var p = DisplayObject.prototype = new createjs.EventDispatcher(); * local Point. */ p.hitTest = function(x, y) { - // TODO: update with support for .hitArea and update hitArea docs? var ctx = DisplayObject._hitTestContext; ctx.setTransform(1, 0, 0, 1, -x, -y); this.draw(ctx); @@ -5816,7 +5964,7 @@ var p = DisplayObject.prototype = new createjs.EventDispatcher(); }; /** - * Provides a chainable shortcut method for setting a number of properties on a DisplayObject instance. + * Provides a chainable shortcut method for setting a number of properties on the instance. * *

Example

* @@ -5826,7 +5974,7 @@ var p = DisplayObject.prototype = new createjs.EventDispatcher(); * * @method set * @param {Object} props A generic object containing properties to copy to the DisplayObject instance. - * @return {DisplayObject} Returns The DisplayObject instance the method is called on (useful for chaining calls.) + * @return {DisplayObject} Returns the instance the method is called on (useful for chaining calls.) */ p.set = function(props) { for (var n in props) { this[n] = props[n]; } @@ -5894,7 +6042,7 @@ var p = DisplayObject.prototype = new createjs.EventDispatcher(); var cacheCanvas = this.cacheCanvas; if (cacheCanvas) { var scale = this._cacheScale; - return this._rectangle.initialize(this._cacheOffsetX, this._cacheOffsetY, cacheCanvas.width/scale, cacheCanvas.height/scale); + return this._rectangle.setValues(this._cacheOffsetX, this._cacheOffsetY, cacheCanvas.width/scale, cacheCanvas.height/scale); } return null; }; @@ -5933,19 +6081,18 @@ var p = DisplayObject.prototype = new createjs.EventDispatcher(); **/ p.setBounds = function(x, y, width, height) { if (x == null) { this._bounds = x; } - this._bounds = (this._bounds || new createjs.Rectangle()).initialize(x, y, width, height); + this._bounds = (this._bounds || new createjs.Rectangle()).setValues(x, y, width, height); }; /** * Returns a clone of this DisplayObject. Some properties that are specific to this instance's current context are - * reverted to their defaults (for example .parent). Also note that caches are not maintained across clones. + * reverted to their defaults (for example .parent). Caches are not maintained across clones, and some elements + * are copied by reference (masks, individual filter instances, hit area) * @method clone * @return {DisplayObject} A clone of the current DisplayObject instance. **/ p.clone = function() { - var o = new DisplayObject(); - this.cloneProps(o); - return o; + return this._cloneProps(new DisplayObject()); }; /** @@ -5957,17 +6104,20 @@ var p = DisplayObject.prototype = new createjs.EventDispatcher(); return "[DisplayObject (name="+ this.name +")]"; }; -// private methods: +// private methods: // separated so it can be used more easily in subclasses: /** - * @method cloneProps - * @protected + * @method _cloneProps * @param {DisplayObject} o The DisplayObject instance which will have properties from the current DisplayObject * instance copied into. + * @return {DisplayObject} o + * @protected **/ - p.cloneProps = function(o) { + p._cloneProps = function(o) { o.alpha = this.alpha; + o.mouseEnabled = this.mouseEnabled; + o.tickEnabled = this.tickEnabled; o.name = this.name; o.regX = this.regX; o.regY = this.regY; @@ -5980,9 +6130,14 @@ var p = DisplayObject.prototype = new createjs.EventDispatcher(); o.visible = this.visible; o.x = this.x; o.y = this.y; - o._bounds = this._bounds; - o.mouseEnabled = this.mouseEnabled; o.compositeOperation = this.compositeOperation; + o.snapToPixel = this.snapToPixel; + o.filters = this.filters==null?null:this.filters.slice(0); + o.mask = this.mask; + o.hitArea = this.hitArea; + o.cursor = this.cursor; + o._bounds = this._bounds; + return o; }; /** @@ -6002,18 +6157,17 @@ var p = DisplayObject.prototype = new createjs.EventDispatcher(); /** * @method _tick - * @param {Array} params Parameters to pass on to any listeners of the tick function. This will usually include the - * properties from the {{#crossLink "Ticker"}}{{/crossLink}} "tick" event, such as `delta` and `paused`, but may - * be undefined or contain other values depending on the usage by the application. + * @param {Object} evtObj An event object that will be dispatched to all tick listeners. This object is reused between dispatchers to reduce construction & GC costs. * @protected **/ - p._tick = function(params) { - // because tick can be really performance sensitive, we'll inline some of the dispatchEvent work. + p._tick = function(evtObj) { + // because tick can be really performance sensitive, check for listeners before calling dispatchEvent. var ls = this._listeners; if (ls && ls["tick"]) { - var evt = new createjs.Event("tick"); - evt.params = params; - this._dispatchEvent(evt, this, 2); + // reset & reuse the event object to avoid construction / GC costs: + evtObj.target = null; + evtObj.propagationStopped = evtObj.immediatePropagationStopped = false; + this.dispatchEvent(evtObj); } }; @@ -6066,7 +6220,7 @@ var p = DisplayObject.prototype = new createjs.EventDispatcher(); var f = this.filters[i]; var fBounds = f.getBounds&&f.getBounds(); if (!fBounds) { continue; } - if (!bounds) { bounds = this._rectangle.initialize(x,y,width,height); } + if (!bounds) { bounds = this._rectangle.setValues(x,y,width,height); } bounds.x += fBounds.x; bounds.y += fBounds.y; bounds.width += fBounds.width; @@ -6096,11 +6250,11 @@ var p = DisplayObject.prototype = new createjs.EventDispatcher(); **/ p._transformBounds = function(bounds, matrix, ignoreTransform) { if (!bounds) { return bounds; } - var x = bounds.x, y = bounds.y, width = bounds.width, height = bounds.height; - var mtx = ignoreTransform ? this._matrix.identity() : this.getMatrix(this._matrix); + var x = bounds.x, y = bounds.y, width = bounds.width, height = bounds.height, mtx = this._props.matrix; + mtx = ignoreTransform ? mtx.identity() : this.getMatrix(mtx); - if (x || y) { mtx.appendTransform(0,0,1,1,0,0,0,-x,-y); } - if (matrix) { mtx.prependMatrix(matrix); } + if (x || y) { mtx.prependTransform(0,0,1,1,0,0,0,-x,-y); } // TODO: simplify this. + if (matrix) { mtx.appendMatrix(matrix); } var x_a = width*mtx.a, x_b = width*mtx.b; var y_c = height*mtx.c, y_d = height*mtx.d; @@ -6116,7 +6270,7 @@ var p = DisplayObject.prototype = new createjs.EventDispatcher(); if ((y = x_b + y_d + ty) < minY) { minY = y; } else if (y > maxY) { maxY = y; } if ((y = y_d + ty) < minY) { minY = y; } else if (y > maxY) { maxY = y; } - return bounds.initialize(minX, minY, maxX-minX, maxY-minY); + return bounds.setValues(minX, minY, maxX-minX, maxY-minY); }; /** @@ -6133,41 +6287,20 @@ var p = DisplayObject.prototype = new createjs.EventDispatcher(); return !!this.cursor; }; -createjs.DisplayObject = DisplayObject; + createjs.DisplayObject = createjs.promote(DisplayObject, "EventDispatcher"); }()); -/* -* Container -* Visit http://createjs.com/ for documentation, updates and examples. -* -* Copyright (c) 2010 gskinner.com, inc. -* -* Permission is hereby granted, free of charge, to any person -* obtaining a copy of this software and associated documentation -* files (the "Software"), to deal in the Software without -* restriction, including without limitation the rights to use, -* copy, modify, merge, publish, distribute, sublicense, and/or sell -* copies of the Software, and to permit persons to whom the -* Software is furnished to do so, subject to the following -* conditions: -* -* The above copyright notice and this permission notice shall be -* included in all copies or substantial portions of the Software. -* -* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES -* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT -* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR -* OTHER DEALINGS IN THE SOFTWARE. -*/ -// namespace: +//############################################################################## +// Container.js +//############################################################################## + this.createjs = this.createjs||{}; (function() { + "use strict"; + +// constructor: /** * A Container is a nestable display list that allows you to work with compound display elements. For example you could * group arm, leg, torso and head {{#crossLink "Bitmap"}}{{/crossLink}} instances together into a Person Container, and @@ -6180,6 +6313,7 @@ this.createjs = this.createjs||{}; * Containers have some overhead, so you generally shouldn't create a Container to hold a single child. * *

Example

+ * * var container = new createjs.Container(); * container.addChild(bitmapInstance, shapeInstance); * container.x = 100; @@ -6188,64 +6322,54 @@ this.createjs = this.createjs||{}; * @extends DisplayObject * @constructor **/ -var Container = function() { - this.initialize(); -}; -var p = Container.prototype = new createjs.DisplayObject(); - -// public properties: - /** - * The array of children in the display list. You should usually use the child management methods such as - * {{#crossLink "Container/addChild"}}{{/crossLink}}, {{#crossLink "Container/removeChild"}}{{/crossLink}}, - * {{#crossLink "Container/swapChildren"}}{{/crossLink}}, etc, rather than accessing this directly, but it is - * included for advanced uses. - * @property children - * @type Array - * @default null - **/ - p.children = null; - - /** - * Indicates whether the children of this container are independently enabled for mouse/pointer interaction. - * If false, the children will be aggregated under the container - for example, a click on a child shape would - * trigger a click event on the container. - * @property mouseChildren - * @type Boolean - * @default true - **/ - p.mouseChildren = true; - - /** - * If false, the tick will not be propagated to children of this Container. This can provide some performance benefits. - * In addition to preventing the "tick" event from being dispatched, it will also prevent tick related updates - * on some display objects (ex. Sprite & MovieClip frame advancing, DOMElement visibility handling). - * @property tickChildren - * @type Boolean - * @default true - **/ - p.tickChildren = true; - -// constructor: - - /** - * @property DisplayObject_initialize - * @type Function - * @private - **/ - p.DisplayObject_initialize = p.initialize; - - /** - * Initialization method. - * @method initialize - * @protected - */ - p.initialize = function() { - this.DisplayObject_initialize(); + function Container() { + this.DisplayObject_constructor(); + + + // public properties: + /** + * The array of children in the display list. You should usually use the child management methods such as + * {{#crossLink "Container/addChild"}}{{/crossLink}}, {{#crossLink "Container/removeChild"}}{{/crossLink}}, + * {{#crossLink "Container/swapChildren"}}{{/crossLink}}, etc, rather than accessing this directly, but it is + * included for advanced uses. + * @property children + * @type Array + * @default null + **/ this.children = []; - }; + + /** + * Indicates whether the children of this container are independently enabled for mouse/pointer interaction. + * If false, the children will be aggregated under the container - for example, a click on a child shape would + * trigger a click event on the container. + * @property mouseChildren + * @type Boolean + * @default true + **/ + this.mouseChildren = true; + + /** + * If false, the tick will not be propagated to children of this Container. This can provide some performance benefits. + * In addition to preventing the "tick" event from being dispatched, it will also prevent tick related updates + * on some display objects (ex. Sprite & MovieClip frame advancing, DOMElement visibility handling). + * @property tickChildren + * @type Boolean + * @default true + **/ + this.tickChildren = true; + } + var p = createjs.extend(Container, createjs.DisplayObject); + // public methods: - + /** + * Constructor alias for backwards compatibility. This method will be removed in future versions. + * Subclasses should be updated to use {{#crossLink "Utility Methods/extends"}}{{/crossLink}}. + * @method initialize + * @deprecated in favour of `createjs.promote()` + **/ + p.initialize = Container; // TODO: deprecated. + /** * Returns true or false indicating whether the display object would be visible if drawn to a canvas. * This does not account for whether it would be visible within the boundaries of the stage. @@ -6259,13 +6383,6 @@ var p = Container.prototype = new createjs.DisplayObject(); return !!(this.visible && this.alpha > 0 && this.scaleX != 0 && this.scaleY != 0 && hasContent); }; - /** - * @property DisplayObject_draw - * @type Function - * @private - **/ - p.DisplayObject_draw = p.draw; - /** * Draws the display object into the specified context ignoring its visible, alpha, shadow, and transform. * Returns true if the draw was handled (useful for overriding functionality). @@ -6299,6 +6416,7 @@ var p = Container.prototype = new createjs.DisplayObject(); * Adds a child to the top of the display list. * *

Example

+ * * container.addChild(bitmapInstance); * * You can also add multiple children at once: @@ -6319,6 +6437,7 @@ var p = Container.prototype = new createjs.DisplayObject(); if (child.parent) { child.parent.removeChild(child); } child.parent = this; this.children.push(child); + child.dispatchEvent("added"); return child; }; @@ -6327,6 +6446,7 @@ var p = Container.prototype = new createjs.DisplayObject(); * setting its parent to this Container. * *

Example

+ * * addChildAt(child1, index); * * You can also add multiple children, such as: @@ -6356,6 +6476,7 @@ var p = Container.prototype = new createjs.DisplayObject(); if (child.parent) { child.parent.removeChild(child); } child.parent = this; this.children.splice(index, 0, child); + child.dispatchEvent("added"); return child; }; @@ -6364,6 +6485,7 @@ var p = Container.prototype = new createjs.DisplayObject(); * already known. * *

Example

+ * * container.removeChild(child); * * You can also remove multiple children: @@ -6415,6 +6537,7 @@ var p = Container.prototype = new createjs.DisplayObject(); var child = this.children[index]; if (child) { child.parent = null; } this.children.splice(index, 1); + child.dispatchEvent("removed"); return true; }; @@ -6422,19 +6545,21 @@ var p = Container.prototype = new createjs.DisplayObject(); * Removes all children from the display list. * *

Example

+ * * container.removeAlLChildren(); * * @method removeAllChildren **/ p.removeAllChildren = function() { var kids = this.children; - while (kids.length) { kids.pop().parent = null; } + while (kids.length) { this.removeChildAt(0); } }; /** * Returns the child at the specified index. * *

Example

+ * * container.getChildAt(2); * * @method getChildAt @@ -6483,6 +6608,7 @@ var p = Container.prototype = new createjs.DisplayObject(); * Returns the index of the specified child in the display list, or -1 if it is not in the display list. * *

Example

+ * * var index = container.getChildIndex(child); * * @method getChildIndex @@ -6590,39 +6716,43 @@ var p = Container.prototype = new createjs.DisplayObject(); * of visual depth, with the top-most display object at index 0. This uses shape based hit detection, and can be an * expensive operation to run, so it is best to use it carefully. For example, if testing for objects under the * mouse, test on tick (instead of on mousemove), and only if the mouse's position has changed. + * + * By default this method evaluates all display objects. By setting the `mode` parameter to `1`, the `mouseEnabled` + * and `mouseChildren` properties will be respected. + * Setting it to `2` additionally excludes display objects that do not have active mouse event listeners + * or a `cursor` property. That is, only objects that would normally intercept mouse interaction will be included. + * This can significantly improve performance in some cases by reducing the number of + * display objects that need to be tested. + * + * Accounts for both {{#crossLink "DisplayObject/hitArea:property"}}{{/crossLink}} and {{#crossLink "DisplayObject/mask:property"}}{{/crossLink}}. * @method getObjectsUnderPoint * @param {Number} x The x position in the container to test. * @param {Number} y The y position in the container to test. + * @param {Number} mode The mode to use to determine which display objects to include. 0-all, 1-respect mouseEnabled/mouseChildren, 2-only mouse opaque objects. * @return {Array} An Array of DisplayObjects under the specified coordinates. **/ - p.getObjectsUnderPoint = function(x, y) { + p.getObjectsUnderPoint = function(x, y, mode) { var arr = []; var pt = this.localToGlobal(x, y); - this._getObjectsUnderPoint(pt.x, pt.y, arr); + this._getObjectsUnderPoint(pt.x, pt.y, arr, mode>0, mode==1); return arr; }; /** * Similar to {{#crossLink "Container/getObjectsUnderPoint()"}}{{/crossLink}}, but returns only the top-most display - * object. This runs significantly faster than getObjectsUnderPoint(), but is still an expensive + * object. This runs significantly faster than getObjectsUnderPoint(), but is still potentially an expensive * operation. See {{#crossLink "Container/getObjectsUnderPoint"}}{{/crossLink}} for more information. * @method getObjectUnderPoint * @param {Number} x The x position in the container to test. * @param {Number} y The y position in the container to test. + * @param {Number} mode The mode to use to determine which display objects to include. 0-all, 1-respect mouseEnabled/mouseChildren, 2-only mouse opaque objects. * @return {DisplayObject} The top-most display object under the specified coordinates. **/ - p.getObjectUnderPoint = function(x, y) { + p.getObjectUnderPoint = function(x, y, mode) { var pt = this.localToGlobal(x, y); - return this._getObjectsUnderPoint(pt.x, pt.y); + return this._getObjectsUnderPoint(pt.x, pt.y, null, mode>0, mode==1); }; - /** - * @property DisplayObject_getBounds - * @type Function - * @protected - **/ - p.DisplayObject_getBounds = p.getBounds; - /** * Docced in superclass. */ @@ -6647,16 +6777,8 @@ var p = Container.prototype = new createjs.DisplayObject(); * @return {Container} A clone of the current Container instance. **/ p.clone = function(recursive) { - var o = new Container(); - this.cloneProps(o); - if (recursive) { - var arr = o.children = []; - for (var i=0, l=this.children.length; i=0; i--) { var child = this.children[i]; - if (child.tickEnabled && child._tick) { child._tick(params); } + if (child.tickEnabled && child._tick) { child._tick(evtObj); } } } - this.DisplayObject__tick(params); + this.DisplayObject__tick(evtObj); + }; + + /** + * Recursively clones all children of this container, and adds them to the target container. + * @method cloneChildren + * @protected + * @param {Container} o The target container. + **/ + p._cloneChildren = function(o) { + if (o.children.length) { o.removeAllChildren(); } + var arr = o.children; + for (var i=0, l=this.children.length; i=0; i--) { var child = children[i]; var hitArea = child.hitArea; if (!child.visible || (!hitArea && !child.isVisible()) || (mouse && !child.mouseEnabled)) { continue; } + if (!hitArea && !this._testMask(child, x, y)) { continue; } + // if a child container has a hitArea then we only need to check its hitArea, so we can treat it as a normal DO: if (!hitArea && child instanceof Container) { - var result = child._getObjectsUnderPoint(x, y, arr, mouse, activeListener); + var result = child._getObjectsUnderPoint(x, y, arr, mouse, activeListener, currentDepth+1); if (!arr && result) { return (mouse && !this.mouseChildren) ? this : result; } } else { if (mouse && !activeListener && !child._hasMouseEventListener()) { continue; } - child.getConcatenatedMatrix(mtx); + // TODO: can we pass displayProps forward, to avoid having to calculate this backwards every time? It's kind of a mixed bag. When we're only hunting for DOs with event listeners, it may not make sense. + var props = child.getConcatenatedDisplayProps(child._props); + mtx = props.matrix; if (hitArea) { - mtx.appendTransform(hitArea.x, hitArea.y, hitArea.scaleX, hitArea.scaleY, hitArea.rotation, hitArea.skewX, hitArea.skewY, hitArea.regX, hitArea.regY); - mtx.alpha = hitArea.alpha; + mtx.prependMatrix(hitArea.getMatrix(hitArea._props.matrix)); + props.alpha = hitArea.alpha; } - ctx.globalAlpha = mtx.alpha; + ctx.globalAlpha = props.alpha; ctx.setTransform(mtx.a, mtx.b, mtx.c, mtx.d, mtx.tx-x, mtx.ty-y); (hitArea||child).draw(ctx); if (!this._testHit(ctx)) { continue; } @@ -6742,6 +6878,37 @@ var p = Container.prototype = new createjs.DisplayObject(); return null; }; + /** + * @method _testMask + * @param {DisplayObject} target + * @param {Number} x + * @param {Number} y + * @return {Boolean} Indicates whether the x/y is within the masked region. + * @protected + **/ + p._testMask = function(target, x, y) { + var mask = target.mask; + if (!mask || !mask.graphics || mask.graphics.isEmpty()) { return true; } + + var mtx = this._props.matrix, parent = target.parent; + mtx = parent ? parent.getConcatenatedMatrix(mtx) : mtx.identity(); + mtx = mask.getMatrix(mask._props.matrix).appendMatrix(mtx); + + var ctx = createjs.DisplayObject._hitTestContext; + ctx.setTransform(mtx.a, mtx.b, mtx.c, mtx.d, mtx.tx-x, mtx.ty-y); + + // draw the mask as a solid fill: + mask.graphics.drawAsPath(ctx); + ctx.fillStyle = "#000"; + ctx.fill(); + + if (!this._testHit(ctx)) { return false; } + ctx.setTransform(1, 0, 0, 1, 0, 0); + ctx.clearRect(0, 0, 2, 2); + + return true; + }; + /** * @method _getBounds * @param {Matrix2D} matrix @@ -6753,93 +6920,219 @@ var p = Container.prototype = new createjs.DisplayObject(); var bounds = this.DisplayObject_getBounds(); if (bounds) { return this._transformBounds(bounds, matrix, ignoreTransform); } - var minX, maxX, minY, maxY; - var mtx = ignoreTransform ? this._matrix.identity() : this.getMatrix(this._matrix); - if (matrix) { mtx.prependMatrix(matrix); } + var mtx = this._props.matrix; + mtx = ignoreTransform ? mtx.identity() : this.getMatrix(mtx); + if (matrix) { mtx.appendMatrix(matrix); } - var l = this.children.length; + var l = this.children.length, rect=null; for (var i=0; i maxX || maxX == null) { maxX = x2; } - if (y1 < minY || minY == null) { minY = y1; } - if (y2 > maxY || maxY == null) { maxY = y2; } + if (rect) { rect.extend(bounds.x, bounds.y, bounds.width, bounds.height); } + else { rect = bounds.clone(); } } - - return (maxX == null) ? null : this._rectangle.initialize(minX, minY, maxX-minX, maxY-minY); + return rect; }; -createjs.Container = Container; -}());/* -* Stage -* Visit http://createjs.com/ for documentation, updates and examples. -* -* Copyright (c) 2010 gskinner.com, inc. -* -* Permission is hereby granted, free of charge, to any person -* obtaining a copy of this software and associated documentation -* files (the "Software"), to deal in the Software without -* restriction, including without limitation the rights to use, -* copy, modify, merge, publish, distribute, sublicense, and/or sell -* copies of the Software, and to permit persons to whom the -* Software is furnished to do so, subject to the following -* conditions: -* -* The above copyright notice and this permission notice shall be -* included in all copies or substantial portions of the Software. -* -* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES -* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT -* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR -* OTHER DEALINGS IN THE SOFTWARE. -*/ -/** - * @module EaselJS - */ + createjs.Container = createjs.promote(Container, "DisplayObject"); +}()); + +//############################################################################## +// Stage.js +//############################################################################## -// namespace: this.createjs = this.createjs||{}; (function() { "use strict"; -/** - * A stage is the root level {{#crossLink "Container"}}{{/crossLink}} for a display list. Each time its {{#crossLink "Stage/tick"}}{{/crossLink}} - * method is called, it will render its display list to its target canvas. - * - *

Example

- * This example creates a stage, adds a child to it, then uses {{#crossLink "Ticker"}}{{/crossLink}} to update the child - * and redraw the stage using {{#crossLink "Stage/update"}}{{/crossLink}}. - * - * var stage = new createjs.Stage("canvasElementId"); - * var image = new createjs.Bitmap("imagePath.png"); - * stage.addChild(image); - * createjs.Ticker.addEventListener("tick", handleTick); - * function handleTick(event) { - * image.x += 10; - * stage.update(); - * } - * - * @class Stage - * @extends Container - * @constructor - * @param {HTMLCanvasElement | String | Object} canvas A canvas object that the Stage will render to, or the string id - * of a canvas object in the current document. - **/ -var Stage = function(canvas) { - this.initialize(canvas); -}; -var p = Stage.prototype = new createjs.Container(); + +// constructor: + /** + * A stage is the root level {{#crossLink "Container"}}{{/crossLink}} for a display list. Each time its {{#crossLink "Stage/tick"}}{{/crossLink}} + * method is called, it will render its display list to its target canvas. + * + *

Example

+ * This example creates a stage, adds a child to it, then uses {{#crossLink "Ticker"}}{{/crossLink}} to update the child + * and redraw the stage using {{#crossLink "Stage/update"}}{{/crossLink}}. + * + * var stage = new createjs.Stage("canvasElementId"); + * var image = new createjs.Bitmap("imagePath.png"); + * stage.addChild(image); + * createjs.Ticker.addEventListener("tick", handleTick); + * function handleTick(event) { + * image.x += 10; + * stage.update(); + * } + * + * @class Stage + * @extends Container + * @constructor + * @param {HTMLCanvasElement | String | Object} canvas A canvas object that the Stage will render to, or the string id + * of a canvas object in the current document. + **/ + function Stage(canvas) { + this.Container_constructor(); + + + // public properties: + /** + * Indicates whether the stage should automatically clear the canvas before each render. You can set this to false + * to manually control clearing (for generative art, or when pointing multiple stages at the same canvas for + * example). + * + *

Example

+ * + * var stage = new createjs.Stage("canvasId"); + * stage.autoClear = false; + * + * @property autoClear + * @type Boolean + * @default true + **/ + this.autoClear = true; + + /** + * The canvas the stage will render to. Multiple stages can share a single canvas, but you must disable autoClear for all but the + * first stage that will be ticked (or they will clear each other's render). + * + * When changing the canvas property you must disable the events on the old canvas, and enable events on the + * new canvas or mouse events will not work as expected. For example: + * + * myStage.enableDOMEvents(false); + * myStage.canvas = anotherCanvas; + * myStage.enableDOMEvents(true); + * + * @property canvas + * @type HTMLCanvasElement | Object + **/ + this.canvas = (typeof canvas == "string") ? document.getElementById(canvas) : canvas; + + /** + * The current mouse X position on the canvas. If the mouse leaves the canvas, this will indicate the most recent + * position over the canvas, and mouseInBounds will be set to false. + * @property mouseX + * @type Number + * @readonly + **/ + this.mouseX = 0; + + /** + * The current mouse Y position on the canvas. If the mouse leaves the canvas, this will indicate the most recent + * position over the canvas, and mouseInBounds will be set to false. + * @property mouseY + * @type Number + * @readonly + **/ + this.mouseY = 0; + + /** + * Specifies the area of the stage to affect when calling update. This can be use to selectively + * re-draw specific regions of the canvas. If null, the whole canvas area is drawn. + * @property drawRect + * @type {Rectangle} + */ + this.drawRect = null; + + /** + * Indicates whether display objects should be rendered on whole pixels. You can set the + * {{#crossLink "DisplayObject/snapToPixel"}}{{/crossLink}} property of + * display objects to false to enable/disable this behaviour on a per instance basis. + * @property snapToPixelEnabled + * @type Boolean + * @default false + **/ + this.snapToPixelEnabled = false; + + /** + * Indicates whether the mouse is currently within the bounds of the canvas. + * @property mouseInBounds + * @type Boolean + * @default false + **/ + this.mouseInBounds = false; + + /** + * If true, tick callbacks will be called on all display objects on the stage prior to rendering to the canvas. + * @property tickOnUpdate + * @type Boolean + * @default true + **/ + this.tickOnUpdate = true; + + /** + * If true, mouse move events will continue to be called when the mouse leaves the target canvas. See + * {{#crossLink "Stage/mouseInBounds:property"}}{{/crossLink}}, and {{#crossLink "MouseEvent"}}{{/crossLink}} + * x/y/rawX/rawY. + * @property mouseMoveOutside + * @type Boolean + * @default false + **/ + this.mouseMoveOutside = false; + + /** + * The hitArea property is not supported for Stage. + * @property hitArea + * @type {DisplayObject} + * @default null + */ + + + // private properties: + /** + * Holds objects with data for each active pointer id. Each object has the following properties: + * x, y, event, target, overTarget, overX, overY, inBounds, posEvtObj (native event that last updated position) + * @property _pointerData + * @type {Object} + * @private + */ + this._pointerData = {}; + + /** + * Number of active pointers. + * @property _pointerCount + * @type {Object} + * @private + */ + this._pointerCount = 0; + + /** + * The ID of the primary pointer. + * @property _primaryPointerID + * @type {Object} + * @private + */ + this._primaryPointerID = null; + + /** + * @property _mouseOverIntervalID + * @protected + * @type Number + **/ + this._mouseOverIntervalID = null; + + /** + * @property _nextStage + * @protected + * @type Stage + **/ + this._nextStage = null; + + /** + * @property _prevStage + * @protected + * @type Stage + **/ + this._prevStage = null; + + + // initialize: + this.enableDOMEvents(true); + } + var p = createjs.extend(Stage, createjs.Container); // events: - /** * Dispatched when the user moves the mouse over the canvas. * See the {{#crossLink "MouseEvent"}}{{/crossLink}} class for a listing of event properties. @@ -6856,6 +7149,7 @@ var p = Stage.prototype = new createjs.Container(); /** * Dispatched when the user the user releases the mouse button anywhere that the page can detect it (this varies slightly between browsers). + * You can use {{#crossLink "Stage/mouseInBounds:property"}}{{/crossLink}} to check whether the mouse is currently within the stage bounds. * See the {{#crossLink "MouseEvent"}}{{/crossLink}} class for a listing of event properties. * @event stagemouseup * @since 0.6.0 @@ -6878,8 +7172,8 @@ var p = Stage.prototype = new createjs.Container(); */ /** - * Dispatched each update immediately before the tick event is propagated through the display list. Does not fire if - * tickOnUpdate is false. + * Dispatched each update immediately before the tick event is propagated through the display list. + * You can call preventDefault on the event object to cancel propagating the tick event. * @event tickstart * @since 0.7.0 */ @@ -6893,6 +7187,7 @@ var p = Stage.prototype = new createjs.Container(); /** * Dispatched each update immediately before the canvas is cleared and the display list is drawn to it. + * You can call preventDefault on the event object to cancel the draw. * @event drawstart * @since 0.7.0 */ @@ -6903,122 +7198,6 @@ var p = Stage.prototype = new createjs.Container(); * @since 0.7.0 */ -// public properties: - /** - * Indicates whether the stage should automatically clear the canvas before each render. You can set this to false - * to manually control clearing (for generative art, or when pointing multiple stages at the same canvas for - * example). - * - *

Example

- * - * var stage = new createjs.Stage("canvasId"); - * stage.autoClear = false; - * - * @property autoClear - * @type Boolean - * @default true - **/ - p.autoClear = true; - - /** - * The canvas the stage will render to. Multiple stages can share a single canvas, but you must disable autoClear for all but the - * first stage that will be ticked (or they will clear each other's render). - * - * When changing the canvas property you must disable the events on the old canvas, and enable events on the - * new canvas or mouse events will not work as expected. For example: - * - * myStage.enableDOMEvents(false); - * myStage.canvas = anotherCanvas; - * myStage.enableDOMEvents(true); - * - * @property canvas - * @type HTMLCanvasElement | Object - **/ - p.canvas = null; - - /** - * The current mouse X position on the canvas. If the mouse leaves the canvas, this will indicate the most recent - * position over the canvas, and mouseInBounds will be set to false. - * @property mouseX - * @type Number - * @readonly - **/ - p.mouseX = 0; - - /** - * The current mouse Y position on the canvas. If the mouse leaves the canvas, this will indicate the most recent - * position over the canvas, and mouseInBounds will be set to false. - * @property mouseY - * @type Number - * @readonly - **/ - p.mouseY = 0; - - // TODO: deprecated. - /** - * REMOVED. Use {{#crossLink "EventDispatcher/addEventListener"}}{{/crossLink}} and the "{{#crossLink "Stage/stagemousemove:event"}}{{/crossLink}} - * event. - * @property onMouseMove - * @type Function - * @deprecated Use addEventListener and the "stagemousemove" event. - */ - /** - * REMOVED. Use {{#crossLink "EventDispatcher/addEventListener"}}{{/crossLink}} and the {{#crossLink "Stage/stagemouseup:event"}}{{/crossLink}} - * event. - * @property onMouseUp - * @type Function - * @deprecated Use addEventListener and the "stagemouseup" event. - */ - /** - * REMOVED. Use {{#crossLink "EventDispatcher/addEventListener"}}{{/crossLink}} and the {{#crossLink "Stage/stagemousedown:event"}}{{/crossLink}} - * event. - * @property onMouseDown - * @type Function - * @deprecated Use addEventListener and the "stagemousedown" event. - */ - - /** - * Indicates whether display objects should be rendered on whole pixels. You can set the - * {{#crossLink "DisplayObject/snapToPixel"}}{{/crossLink}} property of - * display objects to false to enable/disable this behaviour on a per instance basis. - * @property snapToPixelEnabled - * @type Boolean - * @default false - **/ - p.snapToPixelEnabled = false; - - /** - * Indicates whether the mouse is currently within the bounds of the canvas. - * @property mouseInBounds - * @type Boolean - * @default false - **/ - p.mouseInBounds = false; - - /** - * If true, tick callbacks will be called on all display objects on the stage prior to rendering to the canvas. - * @property tickOnUpdate - * @type Boolean - * @default true - **/ - p.tickOnUpdate = true; - - /** - * If true, mouse move events will continue to be called when the mouse leaves the target canvas. See - * {{#crossLink "Stage/mouseInBounds:property"}}{{/crossLink}}, and {{#crossLink "MouseEvent"}}{{/crossLink}} - * x/y/rawX/rawY. - * @property mouseMoveOutside - * @type Boolean - * @default false - **/ - p.mouseMoveOutside = false; - - /** - * The hitArea property is not supported for Stage. - * @property hitArea - * @type {DisplayObject} - * @default null - */ // getter / setters: /** @@ -7066,111 +7245,80 @@ var p = Stage.prototype = new createjs.Container(); }); } catch (e) {} // TODO: use Log -// private properties: - - /** - * Holds objects with data for each active pointer id. Each object has the following properties: - * x, y, event, target, overTarget, overX, overY, inBounds, posEvtObj (native event that last updated position) - * @property _pointerData - * @type {Object} - * @private - */ - p._pointerData = null; - - /** - * Number of active pointers. - * @property _pointerCount - * @type {Object} - * @private - */ - p._pointerCount = 0; - - /** - * The ID of the primary pointer. - * @property _primaryPointerID - * @type {Object} - * @private - */ - p._primaryPointerID = null; - - /** - * @property _mouseOverIntervalID - * @protected - * @type Number - **/ - p._mouseOverIntervalID = null; - - /** - * @property _nextStage - * @protected - * @type Stage - **/ - p._nextStage = null; - - /** - * @property _prevStage - * @protected - * @type Stage - **/ - p._prevStage = null; - -// constructor: - /** - * @property DisplayObject_initialize - * @type Function - * @private - **/ - p.Container_initialize = p.initialize; - - /** - * Initialization method. - * @method initialize - * @param {HTMLCanvasElement | String | Object} canvas A canvas object, or the string id of a canvas object in the current document. - * @protected - **/ - p.initialize = function(canvas) { - this.Container_initialize(); - this.canvas = (typeof canvas == "string") ? document.getElementById(canvas) : canvas; - this._pointerData = {}; - this.enableDOMEvents(true); - }; // public methods: - /** - * Each time the update method is called, the stage will tick all descendants (see: {{#crossLink "DisplayObject/tick"}}{{/crossLink}}) - * and then render the display list to the canvas. Any parameters passed to `update()` will be passed on to any - * {{#crossLink "DisplayObject/tick:event"}}{{/crossLink}} event handlers. - * - * Some time-based features in EaselJS (for example {{#crossLink "Sprite/framerate"}}{{/crossLink}} require that - * a tick event object (or equivalent) be passed as the first parameter to update(). For example: - * - * Ticker.addEventListener("tick", handleTick); - * function handleTick(evtObj) { - * // do some work here, then update the stage, passing through the event object: - * myStage.update(evtObj); - * } + * Each time the update method is called, the stage will call {{#crossLink "Stage/tick"}}{{/crossLink}} + * unless {{#crossLink "Stage/tickOnUpdate:property"}}{{/crossLink}} is set to false, + * and then render the display list to the canvas. * * @method update - * @param {*} [params]* Params to include when ticking descendants. The first param should usually be a tick event. + * @param {Object} [props] Props object to pass to `tick()`. Should usually be a {{#crossLink "Ticker"}}{{/crossLink}} event object, or similar object with a delta property. **/ - p.update = function(params) { + p.update = function(props) { if (!this.canvas) { return; } - if (this.tickOnUpdate) { - this.dispatchEvent("tickstart"); // TODO: make cancellable? - this.tickEnabled&&this._tick((arguments.length ? arguments : null)); - this.dispatchEvent("tickend"); - } - this.dispatchEvent("drawstart"); // TODO: make cancellable? + if (this.tickOnUpdate) { this.tick(props); } + if (this.dispatchEvent("drawstart")) { return; } createjs.DisplayObject._snapToPixelEnabled = this.snapToPixelEnabled; - if (this.autoClear) { this.clear(); } - var ctx = this.canvas.getContext("2d"); + var r = this.drawRect, ctx = this.canvas.getContext("2d"); + ctx.setTransform(1, 0, 0, 1, 0, 0); + if (this.autoClear) { + if (r) { ctx.clearRect(r.x, r.y, r.width, r.height); } + else { ctx.clearRect(0, 0, this.canvas.width+1, this.canvas.height+1); } + } ctx.save(); + if (this.drawRect) { + ctx.beginPath(); + ctx.rect(r.x, r.y, r.width, r.height); + ctx.clip(); + } this.updateContext(ctx); this.draw(ctx, false); ctx.restore(); this.dispatchEvent("drawend"); }; + + /** + * Propagates a tick event through the display list. This is automatically called by {{#crossLink "Stage/update"}}{{/crossLink}} + * unless {{#crossLink "Stage/tickOnUpdate:property"}}{{/crossLink}} is set to false. + * + * If a props object is passed to `tick()`, then all of its properties will be copied to the event object that is + * propagated to listeners. + * + * Some time-based features in EaselJS (for example {{#crossLink "Sprite/framerate"}}{{/crossLink}} require that + * a {{#crossLink "Ticker/tick:event"}}{{/crossLink}} event object (or equivalent object with a delta property) be + * passed as the `props` parameter to `tick()`. For example: + * + * Ticker.on("tick", handleTick); + * function handleTick(evtObj) { + * // clone the event object from Ticker, and add some custom data to it: + * var evt = evtObj.clone().set({greeting:"hello", name:"world"}); + * + * // pass it to stage.update(): + * myStage.update(evt); // subsequently calls tick() with the same param + * } + * + * // ... + * myDisplayObject.on("tick", handleDisplayObjectTick); + * function handleDisplayObjectTick(evt) { + * console.log(evt.delta); // the delta property from the Ticker tick event object + * console.log(evt.greeting, evt.name); // custom data: "hello world" + * } + * + * @method tick + * @param {Object} [props] An object with properties that should be copied to the event object. Should usually be a Ticker event object, or similar object with a delta property. + **/ + p.tick = function(props) { + if (!this.tickEnabled || this.dispatchEvent("tickstart")) { return; } + var evtObj = new createjs.Event("tick"); + if (props) { + for (var n in props) { + if (props.hasOwnProperty(n)) { evtObj[n] = props[n]; } + } + } + this._tick(evtObj); + this.dispatchEvent("tickend"); + }; /** * Default event handler that calls the Stage {{#crossLink "Stage/update"}}{{/crossLink}} method when a {{#crossLink "DisplayObject/tick:event"}}{{/crossLink}} @@ -7203,52 +7351,28 @@ var p = Stage.prototype = new createjs.Container(); * Returns a data url that contains a Base64-encoded image of the contents of the stage. The returned data url can * be specified as the src value of an image element. * @method toDataURL - * @param {String} backgroundColor The background color to be used for the generated image. The value can be any value HTML color - * value, including HEX colors, rgb and rgba. The default value is a transparent background. - * @param {String} mimeType The MIME type of the image format to be create. The default is "image/png". If an unknown MIME type + * @param {String} [backgroundColor] The background color to be used for the generated image. Any valid CSS color + * value is allowed. The default value is a transparent background. + * @param {String} [mimeType="image/png"] The MIME type of the image format to be create. The default is "image/png". If an unknown MIME type * is passed in, or if the browser does not support the specified MIME type, the default value will be used. * @return {String} a Base64 encoded image. **/ p.toDataURL = function(backgroundColor, mimeType) { - if(!mimeType) { - mimeType = "image/png"; - } + var data, ctx = this.canvas.getContext('2d'), w = this.canvas.width, h = this.canvas.height; - var ctx = this.canvas.getContext('2d'); - var w = this.canvas.width; - var h = this.canvas.height; - - var data; - - if(backgroundColor) { - - //get the current ImageData for the canvas. + if (backgroundColor) { data = ctx.getImageData(0, 0, w, h); - - //store the current globalCompositeOperation var compositeOperation = ctx.globalCompositeOperation; - - //set to draw behind current content ctx.globalCompositeOperation = "destination-over"; - - //set background color + ctx.fillStyle = backgroundColor; - - //draw background on entire canvas ctx.fillRect(0, 0, w, h); } - //get the image data from the canvas - var dataURL = this.canvas.toDataURL(mimeType); + var dataURL = this.canvas.toDataURL(mimeType||"image/png"); if(backgroundColor) { - //clear the canvas - ctx.clearRect (0, 0, w+1, h+1); - - //restore it with original settings ctx.putImageData(data, 0, 0); - - //reset the globalCompositeOperation to what it was ctx.globalCompositeOperation = compositeOperation; } @@ -7263,6 +7387,7 @@ var p = Stage.prototype = new createjs.Container(); * independently of mouse move events via the optional `frequency` parameter. * *

Example

+ * * var stage = new createjs.Stage("canvasId"); * stage.enableMouseOver(10); // 10 updates per second * @@ -7326,14 +7451,11 @@ var p = Stage.prototype = new createjs.Container(); }; /** - * Returns a clone of this Stage. + * Stage instances cannot be cloned. * @method clone - * @return {Stage} A clone of the current Container instance. **/ p.clone = function() { - var o = new Stage(null); - this.cloneProps(o); - return o; + throw("Stage cannot be cloned."); }; /** @@ -7345,8 +7467,8 @@ var p = Stage.prototype = new createjs.Container(); return "[Stage (name="+ this.name +")]"; }; - // private methods: +// private methods: /** * @method _getElementRect * @protected @@ -7360,7 +7482,7 @@ var p = Stage.prototype = new createjs.Container(); var offX = (window.pageXOffset || document.scrollLeft || 0) - (document.clientLeft || document.body.clientLeft || 0); var offY = (window.pageYOffset || document.scrollTop || 0) - (document.clientTop || document.body.clientTop || 0); - var styles = window.getComputedStyle ? getComputedStyle(e) : e.currentStyle; // IE <9 compatibility. + var styles = window.getComputedStyle ? getComputedStyle(e,null) : e.currentStyle; // IE <9 compatibility. var padL = parseInt(styles.paddingLeft)+parseInt(styles.borderLeftWidth); var padT = parseInt(styles.paddingTop)+parseInt(styles.borderTopWidth); var padR = parseInt(styles.paddingRight)+parseInt(styles.borderRightWidth); @@ -7643,122 +7765,75 @@ var p = Stage.prototype = new createjs.Container(); target.dispatchEvent(evt); }; -createjs.Stage = Stage; + + createjs.Stage = createjs.promote(Stage, "Container"); }()); -/* -* Bitmap -* Visit http://createjs.com/ for documentation, updates and examples. -* -* Copyright (c) 2010 gskinner.com, inc. -* -* Permission is hereby granted, free of charge, to any person -* obtaining a copy of this software and associated documentation -* files (the "Software"), to deal in the Software without -* restriction, including without limitation the rights to use, -* copy, modify, merge, publish, distribute, sublicense, and/or sell -* copies of the Software, and to permit persons to whom the -* Software is furnished to do so, subject to the following -* conditions: -* -* The above copyright notice and this permission notice shall be -* included in all copies or substantial portions of the Software. -* -* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES -* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT -* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR -* OTHER DEALINGS IN THE SOFTWARE. -*/ -/** -* @module EaselJS -*/ +//############################################################################## +// Bitmap.js +//############################################################################## -// namespace: this.createjs = this.createjs||{}; (function() { - -/** - * A Bitmap represents an Image, Canvas, or Video in the display list. A Bitmap can be instantiated using an existing - * HTML element, or a string. - * - *

Example

- * var bitmap = new createjs.Bitmap("imagePath.jpg"); - * - * Notes: - *
    - *
  1. When a string path or image tag that is not yet loaded is used, the stage may need to be redrawn before it - * will be displayed.
  2. - *
  3. Bitmaps with an SVG source currently will not respect an alpha value other than 0 or 1. To get around this, - * the Bitmap can be cached.
  4. - *
  5. Bitmaps with an SVG source will taint the canvas with cross-origin data, which prevents interactivity. This - * happens in all browsers except recent Firefox builds.
  6. - *
  7. Images loaded cross-origin will throw cross-origin security errors when interacted with using a mouse, using - * methods such as `getObjectUnderPoint`, or using filters, or caching. You can get around this by setting - * `crossOrigin` flags on your images before passing them to EaselJS, eg: `img.crossOrigin="Anonymous";`
  8. - *
- * - * @class Bitmap - * @extends DisplayObject - * @constructor - * @param {Image | HTMLCanvasElement | HTMLVideoElement | String} imageOrUri The source object or URI to an image to - * display. This can be either an Image, Canvas, or Video object, or a string URI to an image file to load and use. - * If it is a URI, a new Image object will be constructed and assigned to the .image property. - **/ -var Bitmap = function(imageOrUri) { - this.initialize(imageOrUri); -}; -var p = Bitmap.prototype = new createjs.DisplayObject(); - -// public properties: - /** - * The image to render. This can be an Image, a Canvas, or a Video. - * @property image - * @type Image | HTMLCanvasElement | HTMLVideoElement - **/ - p.image = null; - - /** - * Specifies an area of the source image to draw. If omitted, the whole image will be drawn. - * @property sourceRect - * @type Rectangle - * @default null - */ - p.sourceRect = null; - // constructor: - /** - * @property DisplayObject_initialize - * @type Function - * @private - **/ - p.DisplayObject_initialize = p.initialize; - - /** - * Initialization method. - * @method initialize + * A Bitmap represents an Image, Canvas, or Video in the display list. A Bitmap can be instantiated using an existing + * HTML element, or a string. + * + *

Example

+ * + * var bitmap = new createjs.Bitmap("imagePath.jpg"); + * + * Notes: + *
    + *
  1. When a string path or image tag that is not yet loaded is used, the stage may need to be redrawn before it + * will be displayed.
  2. + *
  3. Bitmaps with an SVG source currently will not respect an alpha value other than 0 or 1. To get around this, + * the Bitmap can be cached.
  4. + *
  5. Bitmaps with an SVG source will taint the canvas with cross-origin data, which prevents interactivity. This + * happens in all browsers except recent Firefox builds.
  6. + *
  7. Images loaded cross-origin will throw cross-origin security errors when interacted with using a mouse, using + * methods such as `getObjectUnderPoint`, or using filters, or caching. You can get around this by setting + * `crossOrigin` flags on your images before passing them to EaselJS, eg: `img.crossOrigin="Anonymous";`
  8. + *
+ * + * @class Bitmap + * @extends DisplayObject + * @constructor * @param {Image | HTMLCanvasElement | HTMLVideoElement | String} imageOrUri The source object or URI to an image to * display. This can be either an Image, Canvas, or Video object, or a string URI to an image file to load and use. - * If it is a URI, a new Image object will be constructed and assigned to the `.image` property. - * @protected + * If it is a URI, a new Image object will be constructed and assigned to the .image property. **/ - p.initialize = function(imageOrUri) { - this.DisplayObject_initialize(); + function Bitmap(imageOrUri) { + this.DisplayObject_constructor(); + + + // public properties: + /** + * The image to render. This can be an Image, a Canvas, or a Video. + * @property image + * @type Image | HTMLCanvasElement | HTMLVideoElement + **/ if (typeof imageOrUri == "string") { this.image = document.createElement("img"); this.image.src = imageOrUri; } else { this.image = imageOrUri; } - }; + + /** + * Specifies an area of the source image to draw. If omitted, the whole image will be drawn. + * @property sourceRect + * @type Rectangle + * @default null + */ + this.sourceRect = null; + } + var p = createjs.extend(Bitmap, createjs.DisplayObject); + // public methods: - /** * Returns true or false indicating whether the display object would be visible if drawn to a canvas. * This does not account for whether it would be visible within the boundaries of the stage. @@ -7772,13 +7847,6 @@ var p = Bitmap.prototype = new createjs.DisplayObject(); return !!(this.visible && this.alpha > 0 && this.scaleX != 0 && this.scaleY != 0 && hasContent); }; - /** - * @property DisplayObject_draw - * @type Function - * @protected - **/ - p.DisplayObject_draw = p.draw; - /** * Draws the display object into the specified context ignoring its visible, alpha, shadow, and transform. * Returns true if the draw was handled (useful for overriding functionality). @@ -7792,12 +7860,18 @@ var p = Bitmap.prototype = new createjs.DisplayObject(); * @return {Boolean} **/ p.draw = function(ctx, ignoreCache) { - if (this.DisplayObject_draw(ctx, ignoreCache)) { return true; } - var rect = this.sourceRect; + if (this.DisplayObject_draw(ctx, ignoreCache) || !this.image.complete) { return true; } + var img = this.image, rect = this.sourceRect; if (rect) { - ctx.drawImage(this.image, rect.x, rect.y, rect.width, rect.height, 0, 0, rect.width, rect.height); + // some browsers choke on out of bound values, so we'll fix them: + var x1 = rect.x, y1 = rect.y, x2 = x1 + rect.width, y2 = y1 + rect.height, x = 0, y = 0, w = img.width, h = img.height; + if (x1 < 0) { x -= x1; x1 = 0; } + if (x2 > w) { x2 = w; } + if (y1 < 0) { y -= y1; y1 = 0; } + if (y2 > h) { y2 = h; } + ctx.drawImage(img, x1, y1, x2-x1, y2-y1, x, y, x2-x1, y2-y1); } else { - ctx.drawImage(this.image, 0, 0); + ctx.drawImage(img, 0, 0); } return true; }; @@ -7834,13 +7908,6 @@ var p = Bitmap.prototype = new createjs.DisplayObject(); * method. * @method uncache **/ - - /** - * @property DisplayObject_getBounds - * @type Function - * @protected - **/ - p.DisplayObject_getBounds = p.getBounds; /** * Docced in superclass. @@ -7850,7 +7917,7 @@ var p = Bitmap.prototype = new createjs.DisplayObject(); if (rect) { return rect; } var o = this.sourceRect || this.image; var hasContent = (this.image && (this.image.complete || this.image.getContext || this.image.readyState >= 2)); - return hasContent ? this._rectangle.initialize(0, 0, o.width, o.height) : null; + return hasContent ? this._rectangle.setValues(0, 0, o.width, o.height) : null; }; /** @@ -7861,7 +7928,7 @@ var p = Bitmap.prototype = new createjs.DisplayObject(); p.clone = function() { var o = new Bitmap(this.image); if (this.sourceRect) { o.sourceRect = this.sourceRect.clone(); } - this.cloneProps(o); + this._cloneProps(o); return o; }; @@ -7874,76 +7941,152 @@ var p = Bitmap.prototype = new createjs.DisplayObject(); return "[Bitmap (name="+ this.name +")]"; }; -// private methods: + + createjs.Bitmap = createjs.promote(Bitmap, "DisplayObject"); +}()); -createjs.Bitmap = Bitmap; -}());/* -* Sprite -* Visit http://createjs.com/ for documentation, updates and examples. -* -* Copyright (c) 2010 gskinner.com, inc. -* -* Permission is hereby granted, free of charge, to any person -* obtaining a copy of this software and associated documentation -* files (the "Software"), to deal in the Software without -* restriction, including without limitation the rights to use, -* copy, modify, merge, publish, distribute, sublicense, and/or sell -* copies of the Software, and to permit persons to whom the -* Software is furnished to do so, subject to the following -* conditions: -* -* The above copyright notice and this permission notice shall be -* included in all copies or substantial portions of the Software. -* -* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES -* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT -* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR -* OTHER DEALINGS IN THE SOFTWARE. -*/ +//############################################################################## +// Sprite.js +//############################################################################## -/** - * @module EaselJS - */ - -// namespace: this.createjs = this.createjs||{}; (function() { "use strict"; -/** - * Displays a frame or sequence of frames (ie. an animation) from a SpriteSheet instance. A sprite sheet is a series of - * images (usually animation frames) combined into a single image. For example, an animation consisting of 8 100x100 - * images could be combined into a 400x200 sprite sheet (4 frames across by 2 high). You can display individual frames, - * play frames as an animation, and even sequence animations together. - * - * See the {{#crossLink "SpriteSheet"}}{{/crossLink}} class for more information on setting up frames and animations. - * - *

Example

- * var instance = new createjs.Sprite(spriteSheet); - * instance.gotoAndStop("frameName"); - * - * Until {{#crossLink "Sprite/gotoAndStop"}}{{/crossLink}} or {{#crossLink "Sprite/gotoAndPlay"}}{{/crossLink}} is called, - * only the first defined frame defined in the sprite sheet will be displayed. - * - * @class Sprite - * @extends DisplayObject - * @constructor - * @param {SpriteSheet} spriteSheet The SpriteSheet instance to play back. This includes the source image(s), frame - * dimensions, and frame data. See {{#crossLink "SpriteSheet"}}{{/crossLink}} for more information. - * @param {String|Number} frameOrAnimation The frame number or animation to play initially. - **/ -var Sprite = function(spriteSheet, frameOrAnimation) { - this.initialize(spriteSheet, frameOrAnimation); -}; -var p = Sprite.prototype = new createjs.DisplayObject(); + +// constructor: + /** + * Displays a frame or sequence of frames (ie. an animation) from a SpriteSheet instance. A sprite sheet is a series of + * images (usually animation frames) combined into a single image. For example, an animation consisting of 8 100x100 + * images could be combined into a 400x200 sprite sheet (4 frames across by 2 high). You can display individual frames, + * play frames as an animation, and even sequence animations together. + * + * See the {{#crossLink "SpriteSheet"}}{{/crossLink}} class for more information on setting up frames and animations. + * + *

Example

+ * + * var instance = new createjs.Sprite(spriteSheet); + * instance.gotoAndStop("frameName"); + * + * Until {{#crossLink "Sprite/gotoAndStop"}}{{/crossLink}} or {{#crossLink "Sprite/gotoAndPlay"}}{{/crossLink}} is called, + * only the first defined frame defined in the sprite sheet will be displayed. + * + * @class Sprite + * @extends DisplayObject + * @constructor + * @param {SpriteSheet} spriteSheet The SpriteSheet instance to play back. This includes the source image(s), frame + * dimensions, and frame data. See {{#crossLink "SpriteSheet"}}{{/crossLink}} for more information. + * @param {String|Number} [frameOrAnimation] The frame number or animation to play initially. + **/ + function Sprite(spriteSheet, frameOrAnimation) { + this.DisplayObject_constructor(); + + + // public properties: + /** + * The frame index that will be drawn when draw is called. Note that with some {{#crossLink "SpriteSheet"}}{{/crossLink}} + * definitions, this will advance non-sequentially. This will always be an integer value. + * @property currentFrame + * @type {Number} + * @default 0 + * @readonly + **/ + this.currentFrame = 0; + + /** + * Returns the name of the currently playing animation. + * @property currentAnimation + * @type {String} + * @final + * @readonly + **/ + this.currentAnimation = null; + + /** + * Prevents the animation from advancing each tick automatically. For example, you could create a sprite + * sheet of icons, set paused to true, and display the appropriate icon by setting currentFrame. + * @property paused + * @type {Boolean} + * @default false + **/ + this.paused = true; + + /** + * The SpriteSheet instance to play back. This includes the source image, frame dimensions, and frame + * data. See {{#crossLink "SpriteSheet"}}{{/crossLink}} for more information. + * @property spriteSheet + * @type {SpriteSheet} + * @readonly + **/ + this.spriteSheet = spriteSheet; + + /** + * Specifies the current frame index within the currently playing animation. When playing normally, this will increase + * from 0 to n-1, where n is the number of frames in the current animation. + * + * This could be a non-integer value if + * using time-based playback (see {{#crossLink "Sprite/framerate"}}{{/crossLink}}, or if the animation's speed is + * not an integer. + * @property currentAnimationFrame + * @type {Number} + * @default 0 + **/ + this.currentAnimationFrame = 0; + + /** + * By default Sprite instances advance one frame per tick. Specifying a framerate for the Sprite (or its related + * SpriteSheet) will cause it to advance based on elapsed time between ticks as appropriate to maintain the target + * framerate. + * + * For example, if a Sprite with a framerate of 10 is placed on a Stage being updated at 40fps, then the Sprite will + * advance roughly one frame every 4 ticks. This will not be exact, because the time between each tick will + * vary slightly between frames. + * + * This feature is dependent on the tick event object (or an object with an appropriate "delta" property) being + * passed into {{#crossLink "Stage/update"}}{{/crossLink}}. + * @property framerate + * @type {Number} + * @default 0 + **/ + this.framerate = 0; + + + // private properties: + /** + * Current animation object. + * @property _animation + * @protected + * @type {Object} + * @default null + **/ + this._animation = null; + + /** + * Current frame index. + * @property _currentFrame + * @protected + * @type {Number} + * @default null + **/ + this._currentFrame = null; + + /** + * Skips the next auto advance. Used by gotoAndPlay to avoid immediately jumping to the next frame + * @property _skipAdvance + * @protected + * @type {Boolean} + * @default false + **/ + this._skipAdvance = false; + + + if (frameOrAnimation) { this.gotoAndPlay(frameOrAnimation); } + } + var p = createjs.extend(Sprite, createjs.DisplayObject); + // events: - /** * Dispatched when an animation reaches its ends. * @event animationend @@ -7953,136 +8096,17 @@ var p = Sprite.prototype = new createjs.DisplayObject(); * @param {String} next The name of the next animation that will be played, or null. This will be the same as name if the animation is looping. * @since 0.6.0 */ - -// public properties: - // TODO: deprecated. + /** - * REMOVED. Use {{#crossLink "EventDispatcher/addEventListener"}}{{/crossLink}} and the {{#crossLink "Sprite/animationend:event"}}{{/crossLink}} - * event. - * @property onAnimationEnd - * @type {Function} - * @deprecated Use addEventListener and the "animationend" event. + * Dispatched any time the current frame changes. For example, this could be due to automatic advancement on a tick, + * or calling gotoAndPlay() or gotoAndStop(). + * @event change + * @param {Object} target The object that dispatched the event. + * @param {String} type The event type. */ - /** - * The frame index that will be drawn when draw is called. Note that with some {{#crossLink "SpriteSheet"}}{{/crossLink}} - * definitions, this will advance non-sequentially. This will always be an integer value. - * @property currentFrame - * @type {Number} - * @default 0 - * @readonly - **/ - p.currentFrame = 0; - - /** - * Returns the name of the currently playing animation. - * @property currentAnimation - * @type {String} - * @final - * @readonly - **/ - p.currentAnimation = null; - - /** - * Prevents the animation from advancing each tick automatically. For example, you could create a sprite - * sheet of icons, set paused to true, and display the appropriate icon by setting currentFrame. - * @property paused - * @type {Boolean} - * @default false - **/ - p.paused = true; - - /** - * The SpriteSheet instance to play back. This includes the source image, frame dimensions, and frame - * data. See {{#crossLink "SpriteSheet"}}{{/crossLink}} for more information. - * @property spriteSheet - * @type {SpriteSheet} - * @readonly - **/ - p.spriteSheet = null; - - /** - * @property offset - * @type {Number} - * @default 0 - * @deprecated Not applicable to the new timing model in v0.7.0 and higher. - */ - p.offset = 0; - - /** - * Specifies the current frame index within the currently playing animation. When playing normally, this will increase - * from 0 to n-1, where n is the number of frames in the current animation. - * - * This could be a non-integer value if - * using time-based playback (see {{#crossLink "Sprite/framerate"}}{{/crossLink}}, or if the animation's speed is - * not an integer. - * @property currentAnimationFrame - * @type {Number} - * @default 0 - **/ - p.currentAnimationFrame = 0; - - /** - * By default Sprite instances advance one frame per tick. Specifying a framerate for the Sprite (or its related - * SpriteSheet) will cause it to advance based on elapsed time between ticks as appropriate to maintain the target - * framerate. - * - * For example, if a Sprite with a framerate of 10 is placed on a Stage being updated at 40fps, then the Sprite will - * advance roughly one frame every 4 ticks. This will not be exact, because the time between each tick will - * vary slightly between frames. - * - * This feature is dependent on the tick event object (or an object with an appropriate "delta" property) being - * passed into {{#crossLink "Stage/update"}}{{/crossLink}}. - * @property framerate - * @type {Number} - * @default 0 - **/ - p.framerate = 0; - -// private properties: - /** - * @property _advanceCount - * @protected - * @type {Number} - * @default 0 - **/ - p._advanceCount = 0; - - /** - * @property _animation - * @protected - * @type {Object} - * @default null - **/ - p._animation = null; - - /** - * @property _animation - * @protected - * @type {Object} - * @default null - **/ - p._currentFrame = null; - -// constructor: - /** - * @property DisplayObject_initialize - * @type {Function} - * @private - **/ - p.DisplayObject_initialize = p.initialize; - - /** - * Initialization method. - * @method initialize - * @protected - */ - p.initialize = function(spriteSheet, frameOrAnimation) { - this.DisplayObject_initialize(); - this.spriteSheet = spriteSheet; - if (frameOrAnimation) { this.gotoAndPlay(frameOrAnimation); } - }; +// public methods: /** * Returns true or false indicating whether the display object would be visible if drawn to a canvas. * This does not account for whether it would be visible within the boundaries of the stage. @@ -8095,13 +8119,6 @@ var p = Sprite.prototype = new createjs.DisplayObject(); return !!(this.visible && this.alpha > 0 && this.scaleX != 0 && this.scaleY != 0 && hasContent); }; - /** - * @property DisplayObject_draw - * @type {Function} - * @private - **/ - p.DisplayObject_draw = p.draw; - /** * Draws the display object into the specified context ignoring its visible, alpha, shadow, and transform. * Returns true if the draw was handled (useful for overriding functionality). @@ -8118,7 +8135,7 @@ var p = Sprite.prototype = new createjs.DisplayObject(); var o = this.spriteSheet.getFrame(this._currentFrame|0); if (!o) { return false; } var rect = o.rect; - ctx.drawImage(o.image, rect.x, rect.y, rect.width, rect.height, -o.regX, -o.regY, rect.width, rect.height); + if (rect.width && rect.height) { ctx.drawImage(o.image, rect.x, rect.y, rect.width, rect.height, -o.regX, -o.regY, rect.width, rect.height); } return true; }; @@ -8126,20 +8143,20 @@ var p = Sprite.prototype = new createjs.DisplayObject(); //Bitmap. This is why they have no method implementations. /** - * Because the content of a Bitmap is already in a simple format, cache is unnecessary for Bitmap instances. - * You should not cache Bitmap instances as it can degrade performance. + * Because the content of a Sprite is already in a raster format, cache is unnecessary for Sprite instances. + * You should not cache Sprite instances as it can degrade performance. * @method cache **/ /** - * Because the content of a Bitmap is already in a simple format, cache is unnecessary for Bitmap instances. - * You should not cache Bitmap instances as it can degrade performance. + * Because the content of a Sprite is already in a raster format, cache is unnecessary for Sprite instances. + * You should not cache Sprite instances as it can degrade performance. * @method updateCache **/ /** - * Because the content of a Bitmap is already in a simple format, cache is unnecessary for Bitmap instances. - * You should not cache Bitmap instances as it can degrade performance. + * Because the content of a Sprite is already in a raster format, cache is unnecessary for Sprite instances. + * You should not cache Sprite instances as it can degrade performance. * @method uncache **/ @@ -8171,6 +8188,7 @@ var p = Sprite.prototype = new createjs.DisplayObject(); **/ p.gotoAndPlay = function(frameOrAnimation) { this.paused = false; + this._skipAdvance = true; this._goto(frameOrAnimation); }; @@ -8192,21 +8210,11 @@ var p = Sprite.prototype = new createjs.DisplayObject(); * @method advance */ p.advance = function(time) { - var speed = (this._animation&&this._animation.speed)||1; var fps = this.framerate || this.spriteSheet.framerate; var t = (fps && time != null) ? time/(1000/fps) : 1; - if (this._animation) { this.currentAnimationFrame+=t*speed; } - else { this._currentFrame+=t*speed; } - this._normalizeFrame(); + this._normalizeFrame(t); }; - /** - * @property DisplayObject_getBounds - * @type Function - * @protected - **/ - p.DisplayObject_getBounds = p.getBounds; - /** * Returns a {{#crossLink "Rectangle"}}{{/crossLink}} instance defining the bounds of the current frame relative to * the origin. For example, a 90 x 70 frame with regX=50 and regY=40 would return a @@ -8229,9 +8237,7 @@ var p = Sprite.prototype = new createjs.DisplayObject(); * @return {Sprite} a clone of the Sprite instance. **/ p.clone = function() { - var o = new Sprite(this.spriteSheet); - this.cloneProps(o); - return o; + return this._cloneProps(new Sprite(this.spriteSheet)); }; /** @@ -8245,23 +8251,38 @@ var p = Sprite.prototype = new createjs.DisplayObject(); // private methods: /** - * @property DisplayObject__tick - * @type {Function} - * @private + * @method _cloneProps + * @param {Sprite} o + * @return {Sprite} o + * @protected **/ - p.DisplayObject__tick = p._tick; - + p._cloneProps = function(o) { + this.DisplayObject__cloneProps(o); + o.currentFrame = this.currentFrame; + o.currentAnimation = this.currentAnimation; + o.paused = this.paused; + o.currentAnimationFrame = this.currentAnimationFrame; + o.framerate = this.framerate; + + o._animation = this._animation; + o._currentFrame = this._currentFrame; + o._skipAdvance = this._skipAdvance; + return o; + }; + /** * Advances the currentFrame if paused is not true. This is called automatically when the {{#crossLink "Stage"}}{{/crossLink}} * ticks. + * @param {Object} evtObj An event object that will be dispatched to all tick listeners. This object is reused between dispatchers to reduce construction & GC costs. * @protected * @method _tick **/ - p._tick = function(params) { + p._tick = function(evtObj) { if (!this.paused) { - this.advance(params&¶ms[0]&¶ms[0].delta); + if (!this._skipAdvance) { this.advance(evtObj&&evtObj.delta); } + this._skipAdvance = false; } - this.DisplayObject__tick(params); + this.DisplayObject__tick(evtObj); }; @@ -8270,41 +8291,50 @@ var p = Sprite.prototype = new createjs.DisplayObject(); * @protected * @method _normalizeFrame **/ - p._normalizeFrame = function() { + p._normalizeFrame = function(frameDelta) { + frameDelta = frameDelta || 0; var animation = this._animation; var paused = this.paused; var frame = this._currentFrame; - var animFrame = this.currentAnimationFrame; var l; - + if (animation) { + var speed = animation.speed || 1; + var animFrame = this.currentAnimationFrame; l = animation.frames.length; - if ((animFrame|0) >= l) { + if (animFrame + frameDelta * speed >= l) { var next = animation.next; - if (this._dispatchAnimationEnd(animation, frame, paused, next, l-1)) { - // something changed in the event stack. + if (this._dispatchAnimationEnd(animation, frame, paused, next, l - 1)) { + // something changed in the event stack, so we shouldn't make any more changes here. + return; } else if (next) { - // sequence. Automatically calls _normalizeFrame again. - return this._goto(next, animFrame - l); + // sequence. Automatically calls _normalizeFrame again with the remaining frames. + return this._goto(next, frameDelta - (l - animFrame) / speed); } else { // end. this.paused = true; - animFrame = this.currentAnimationFrame = animation.frames.length-1; - this._currentFrame = animation.frames[animFrame]; + animFrame = animation.frames.length - 1; } } else { - this._currentFrame = animation.frames[animFrame|0]; + animFrame += frameDelta * speed; } + this.currentAnimationFrame = animFrame; + this._currentFrame = animation.frames[animFrame | 0] } else { + frame = (this._currentFrame += frameDelta); l = this.spriteSheet.getNumFrames(); - if (frame >= l) { - if (!this._dispatchAnimationEnd(animation, frame, paused, l-1)) { + if (frame >= l && l > 0) { + if (!this._dispatchAnimationEnd(animation, frame, paused, l - 1)) { // looped. if ((this._currentFrame -= l) >= l) { return this._normalizeFrame(); } } } } - this.currentFrame = this._currentFrame|0; + frame = this._currentFrame | 0; + if (this.currentFrame != frame) { + this.currentFrame = frame; + this.dispatchEvent("change"); + } }; /** @@ -8329,29 +8359,6 @@ var p = Sprite.prototype = new createjs.DisplayObject(); return changed; }; - /** - * @property DisplayObject_cloneProps - * @private - * @type {Function} - **/ - p.DisplayObject_cloneProps = p.cloneProps; - - /** - * @method cloneProps - * @param {Text} o - * @protected - **/ - p.cloneProps = function(o) { - this.DisplayObject_cloneProps(o); - o.currentFrame = this.currentFrame; - o._currentFrame = this._currentFrame; - o.currentAnimation = this.currentAnimation; - o.paused = this.paused; - o._animation = this._animation; - o.currentAnimationFrame = this.currentAnimationFrame; - o.framerate = this.framerate; - }; - /** * Moves the playhead to the specified frame number or animation. * @method _goto @@ -8360,167 +8367,75 @@ var p = Sprite.prototype = new createjs.DisplayObject(); * @protected **/ p._goto = function(frameOrAnimation, frame) { + this.currentAnimationFrame = 0; if (isNaN(frameOrAnimation)) { var data = this.spriteSheet.getAnimation(frameOrAnimation); if (data) { - this.currentAnimationFrame = frame||0; this._animation = data; this.currentAnimation = frameOrAnimation; - this._normalizeFrame(); + this._normalizeFrame(frame); } } else { - this.currentAnimationFrame = 0; this.currentAnimation = this._animation = null; this._currentFrame = frameOrAnimation; this._normalizeFrame(); } }; -createjs.Sprite = Sprite; + + createjs.Sprite = createjs.promote(Sprite, "DisplayObject"); }()); -/* -* BitmapAnimation -* Visit http://createjs.com/ for documentation, updates and examples. -* -* Copyright (c) 2010 gskinner.com, inc. -* -* Permission is hereby granted, free of charge, to any person -* obtaining a copy of this software and associated documentation -* files (the "Software"), to deal in the Software without -* restriction, including without limitation the rights to use, -* copy, modify, merge, publish, distribute, sublicense, and/or sell -* copies of the Software, and to permit persons to whom the -* Software is furnished to do so, subject to the following -* conditions: -* -* The above copyright notice and this permission notice shall be -* included in all copies or substantial portions of the Software. -* -* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES -* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT -* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR -* OTHER DEALINGS IN THE SOFTWARE. -*/ -// namespace: -this.createjs = this.createjs||{}; +//############################################################################## +// Shape.js +//############################################################################## -(function() { - "use strict"; -/** - * Deprecated in favour of {{#crossLink "Sprite"}}{{/crossLink}}. - * - * @class BitmapAnimation - * @extends DisplayObject - * @constructor - * @param {SpriteSheet} spriteSheet The SpriteSheet instance to play back. This includes the source image(s), frame - * dimensions, and frame data. See {{#crossLink "SpriteSheet"}}{{/crossLink}} for more information. - * @deprecated Renamed to Sprite. Will be removed in a future version. - **/ - -var e = "BitmapAnimation is deprecated in favour of Sprite. See VERSIONS file for info on changes."; -if (!createjs.Sprite) { throw(e); } -(createjs.BitmapAnimation = function(spriteSheet) { - console.log(e); - this.initialize(spriteSheet); -}).prototype = new createjs.Sprite(); -})(); -/* -* Shape -* Visit http://createjs.com/ for documentation, updates and examples. -* -* Copyright (c) 2010 gskinner.com, inc. -* -* Permission is hereby granted, free of charge, to any person -* obtaining a copy of this software and associated documentation -* files (the "Software"), to deal in the Software without -* restriction, including without limitation the rights to use, -* copy, modify, merge, publish, distribute, sublicense, and/or sell -* copies of the Software, and to permit persons to whom the -* Software is furnished to do so, subject to the following -* conditions: -* -* The above copyright notice and this permission notice shall be -* included in all copies or substantial portions of the Software. -* -* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES -* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT -* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR -* OTHER DEALINGS IN THE SOFTWARE. -*/ - -/** - * @module EaselJS - */ - -// namespace: this.createjs = this.createjs||{}; (function() { "use strict"; -/** - * A Shape allows you to display vector art in the display list. It composites a {{#crossLink "Graphics"}}{{/crossLink}} - * instance which exposes all of the vector drawing methods. The Graphics instance can be shared between multiple Shape - * instances to display the same vector graphics with different positions or transforms. - * - * If the vector art will not - * change between draws, you may want to use the {{#crossLink "DisplayObject/cache"}}{{/crossLink}} method to reduce the - * rendering cost. - * - *

Example

- * var graphics = new createjs.Graphics().beginFill("#ff0000").drawRect(0, 0, 100, 100); - * var shape = new createjs.Shape(graphics); - * - * //Alternatively use can also use the graphics property of the Shape class to renderer the same as above. - * var shape = new createjs.Shape(); - * shape.graphics.beginFill("#ff0000").drawRect(0, 0, 100, 100); - * - * @class Shape - * @extends DisplayObject - * @constructor - * @param {Graphics} graphics Optional. The graphics instance to display. If null, a new Graphics instance will be created. - **/ -var Shape = function(graphics) { - this.initialize(graphics); -} -var p = Shape.prototype = new createjs.DisplayObject(); - -// public properties: - /** - * The graphics instance to display. - * @property graphics - * @type Graphics - **/ - p.graphics = null; // constructor: /** - * @property DisplayObject_initialize - * @private - * @type Function + * A Shape allows you to display vector art in the display list. It composites a {{#crossLink "Graphics"}}{{/crossLink}} + * instance which exposes all of the vector drawing methods. The Graphics instance can be shared between multiple Shape + * instances to display the same vector graphics with different positions or transforms. + * + * If the vector art will not + * change between draws, you may want to use the {{#crossLink "DisplayObject/cache"}}{{/crossLink}} method to reduce the + * rendering cost. + * + *

Example

+ * + * var graphics = new createjs.Graphics().beginFill("#ff0000").drawRect(0, 0, 100, 100); + * var shape = new createjs.Shape(graphics); + * + * //Alternatively use can also use the graphics property of the Shape class to renderer the same as above. + * var shape = new createjs.Shape(); + * shape.graphics.beginFill("#ff0000").drawRect(0, 0, 100, 100); + * + * @class Shape + * @extends DisplayObject + * @constructor + * @param {Graphics} graphics Optional. The graphics instance to display. If null, a new Graphics instance will be created. **/ - p.DisplayObject_initialize = p.initialize; - - /** - * Initialization method. - * @method initialize - * @param {Graphics} graphics - * @protected - **/ - p.initialize = function(graphics) { - this.DisplayObject_initialize(); + function Shape(graphics) { + this.DisplayObject_constructor(); + + + // public properties: + /** + * The graphics instance to display. + * @property graphics + * @type Graphics + **/ this.graphics = graphics ? graphics : new createjs.Graphics(); } + var p = createjs.extend(Shape, createjs.DisplayObject); + +// public methods: /** * Returns true or false indicating whether the Shape would be visible if drawn to a canvas. * This does not account for whether it would be visible within the boundaries of the stage. @@ -8533,13 +8448,6 @@ var p = Shape.prototype = new createjs.DisplayObject(); return !!(this.visible && this.alpha > 0 && this.scaleX != 0 && this.scaleY != 0 && hasContent); }; - /** - * @property DisplayObject_draw - * @private - * @type Function - **/ - p.DisplayObject_draw = p.draw; - /** * Draws the Shape into the specified context ignoring its visible, alpha, shadow, and transform. Returns true if * the draw was handled (useful for overriding functionality). @@ -8553,9 +8461,9 @@ var p = Shape.prototype = new createjs.DisplayObject(); **/ p.draw = function(ctx, ignoreCache) { if (this.DisplayObject_draw(ctx, ignoreCache)) { return true; } - this.graphics.draw(ctx); + this.graphics.draw(ctx, this); return true; - } + }; /** * Returns a clone of this Shape. Some properties that are specific to this instance's current context are reverted to @@ -8565,10 +8473,9 @@ var p = Shape.prototype = new createjs.DisplayObject(); * cloned. If false, the Graphics instance will be shared with the new Shape. **/ p.clone = function(recursive) { - var o = new Shape((recursive && this.graphics) ? this.graphics.clone() : this.graphics); - this.cloneProps(o); - return o; - } + var g = (recursive && this.graphics) ? this.graphics.clone() : this.graphics; + return this._cloneProps(new Shape(g)); + }; /** * Returns a string representation of this object. @@ -8577,81 +8484,135 @@ var p = Shape.prototype = new createjs.DisplayObject(); **/ p.toString = function() { return "[Shape (name="+ this.name +")]"; - } + }; -createjs.Shape = Shape; + + createjs.Shape = createjs.promote(Shape, "DisplayObject"); }()); -/* -* Text -* Visit http://createjs.com/ for documentation, updates and examples. -* -* Copyright (c) 2010 gskinner.com, inc. -* -* Permission is hereby granted, free of charge, to any person -* obtaining a copy of this software and associated documentation -* files (the "Software"), to deal in the Software without -* restriction, including without limitation the rights to use, -* copy, modify, merge, publish, distribute, sublicense, and/or sell -* copies of the Software, and to permit persons to whom the -* Software is furnished to do so, subject to the following -* conditions: -* -* The above copyright notice and this permission notice shall be -* included in all copies or substantial portions of the Software. -* -* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES -* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT -* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR -* OTHER DEALINGS IN THE SOFTWARE. -*/ -/** - * @module EaselJS - */ +//############################################################################## +// Text.js +//############################################################################## -// namespace: this.createjs = this.createjs||{}; (function() { "use strict"; -/** - * Display one or more lines of dynamic text (not user editable) in the display list. Line wrapping support (using the - * lineWidth) is very basic, wrapping on spaces and tabs only. Note that as an alternative to Text, you can position HTML - * text above or below the canvas relative to items in the display list using the {{#crossLink "DisplayObject/localToGlobal"}}{{/crossLink}} - * method, or using {{#crossLink "DOMElement"}}{{/crossLink}}. - * - * Please note that Text does not support HTML text, and can only display one font style at a time. To use - * multiple font styles, you will need to create multiple text instances, and position them manually. - * - *

Example

- * var text = new createjs.Text("Hello World", "20px Arial", "#ff7700"); - * text.x = 100; - * text.textBaseline = "alphabetic"; - * - * CreateJS Text supports web fonts (the same rules as Canvas). The font must be loaded and supported by the browser - * before it can be displayed. - * - * Note: Text can be expensive to generate, so cache instances where possible. Be aware that not all - * browsers will render Text exactly the same. - * @class Text - * @extends DisplayObject - * @constructor - * @param {String} [text] The text to display. - * @param {String} [font] The font style to use. Any valid value for the CSS font attribute is acceptable (ex. "bold - * 36px Arial"). - * @param {String} [color] The color to draw the text in. Any valid value for the CSS color attribute is acceptable (ex. - * "#F00", "red", or "#FF0000"). - **/ -var Text = function(text, font, color) { - this.initialize(text, font, color); -}; -var p = Text.prototype = new createjs.DisplayObject(); +// constructor: + /** + * Display one or more lines of dynamic text (not user editable) in the display list. Line wrapping support (using the + * lineWidth) is very basic, wrapping on spaces and tabs only. Note that as an alternative to Text, you can position HTML + * text above or below the canvas relative to items in the display list using the {{#crossLink "DisplayObject/localToGlobal"}}{{/crossLink}} + * method, or using {{#crossLink "DOMElement"}}{{/crossLink}}. + * + * Please note that Text does not support HTML text, and can only display one font style at a time. To use + * multiple font styles, you will need to create multiple text instances, and position them manually. + * + *

Example

+ * + * var text = new createjs.Text("Hello World", "20px Arial", "#ff7700"); + * text.x = 100; + * text.textBaseline = "alphabetic"; + * + * CreateJS Text supports web fonts (the same rules as Canvas). The font must be loaded and supported by the browser + * before it can be displayed. + * + * Note: Text can be expensive to generate, so cache instances where possible. Be aware that not all + * browsers will render Text exactly the same. + * @class Text + * @extends DisplayObject + * @constructor + * @param {String} [text] The text to display. + * @param {String} [font] The font style to use. Any valid value for the CSS font attribute is acceptable (ex. "bold + * 36px Arial"). + * @param {String} [color] The color to draw the text in. Any valid value for the CSS color attribute is acceptable (ex. + * "#F00", "red", or "#FF0000"). + **/ + function Text(text, font, color) { + this.DisplayObject_constructor(); + + + // public properties: + /** + * The text to display. + * @property text + * @type String + **/ + this.text = text; + + /** + * The font style to use. Any valid value for the CSS font attribute is acceptable (ex. "bold 36px Arial"). + * @property font + * @type String + **/ + this.font = font; + + /** + * The color to draw the text in. Any valid value for the CSS color attribute is acceptable (ex. "#F00"). Default is "#000". + * It will also accept valid canvas fillStyle values. + * @property color + * @type String + **/ + this.color = color; + + /** + * The horizontal text alignment. Any of "start", "end", "left", "right", and "center". For detailed + * information view the + * + * whatwg spec. Default is "left". + * @property textAlign + * @type String + **/ + this.textAlign = "left"; + + /** + * The vertical alignment point on the font. Any of "top", "hanging", "middle", "alphabetic", "ideographic", or + * "bottom". For detailed information view the + * whatwg spec. Default is "top". + * @property textBaseline + * @type String + */ + this.textBaseline = "top"; + + /** + * The maximum width to draw the text. If maxWidth is specified (not null), the text will be condensed or + * shrunk to make it fit in this width. For detailed information view the + * + * whatwg spec. + * @property maxWidth + * @type Number + */ + this.maxWidth = null; + + /** + * If greater than 0, the text will be drawn as a stroke (outline) of the specified width. + * @property outline + * @type Number + **/ + this.outline = 0; + + /** + * Indicates the line height (vertical distance between baselines) for multi-line text. If null or 0, + * the value of getMeasuredLineHeight is used. + * @property lineHeight + * @type Number + **/ + this.lineHeight = 0; + + /** + * Indicates the maximum width for a line of text before it is wrapped to multiple lines. If null, + * the text will not be wrapped. + * @property lineWidth + * @type Number + **/ + this.lineWidth = null; + } + var p = createjs.extend(Text, createjs.DisplayObject); + + +// static properties: /** * @property _workingContext * @type CanvasRenderingContext2D @@ -8660,7 +8621,8 @@ var p = Text.prototype = new createjs.DisplayObject(); var canvas = (createjs.createCanvas?createjs.createCanvas():document.createElement("canvas")); if (canvas.getContext) { Text._workingContext = canvas.getContext("2d"); canvas.width = canvas.height = 1; } -// static properties: + +// constants: /** * Lookup table for the ratio to offset bounds x calculations based on the textAlign property. * @property H_OFFSETS @@ -8679,108 +8641,8 @@ var p = Text.prototype = new createjs.DisplayObject(); **/ Text.V_OFFSETS = {top: 0, hanging: -0.01, middle: -0.4, alphabetic: -0.8, ideographic: -0.85, bottom: -1}; -// public properties: - /** - * The text to display. - * @property text - * @type String - **/ - p.text = ""; - - /** - * The font style to use. Any valid value for the CSS font attribute is acceptable (ex. "bold 36px Arial"). - * @property font - * @type String - **/ - p.font = null; - - /** - * The color to draw the text in. Any valid value for the CSS color attribute is acceptable (ex. "#F00"). Default is "#000". - * It will also accept valid canvas fillStyle values. - * @property color - * @type String - **/ - p.color = null; - - /** - * The horizontal text alignment. Any of "start", "end", "left", "right", and "center". For detailed - * information view the - * - * whatwg spec. Default is "left". - * @property textAlign - * @type String - **/ - p.textAlign = "left"; - - /** - * The vertical alignment point on the font. Any of "top", "hanging", "middle", "alphabetic", "ideographic", or - * "bottom". For detailed information view the - * whatwg spec. Default is "top". - * @property textBaseline - * @type String - */ - p.textBaseline = "top"; - - /** - * The maximum width to draw the text. If maxWidth is specified (not null), the text will be condensed or - * shrunk to make it fit in this width. For detailed information view the - * - * whatwg spec. - * @property maxWidth - * @type Number - */ - p.maxWidth = null; - - /** - * If greater than 0, the text will be drawn as a stroke (outline) of the specified width. - * @property outline - * @type Number - **/ - p.outline = 0; - - /** - * Indicates the line height (vertical distance between baselines) for multi-line text. If null or 0, - * the value of getMeasuredLineHeight is used. - * @property lineHeight - * @type Number - **/ - p.lineHeight = 0; - - /** - * Indicates the maximum width for a line of text before it is wrapped to multiple lines. If null, - * the text will not be wrapped. - * @property lineWidth - * @type Number - **/ - p.lineWidth = null; - -// private properties: - -// constructor: - /** - * @property DisplayObject_initialize - * @private - * @type Function - **/ - p.DisplayObject_initialize = p.initialize; - - /** - * Initialization method. - * @method initialize - * @param {String} [text] The text to display. - * @param {String} [font] The font style to use. Any valid value for the CSS font attribute is acceptable (ex. "bold - * 36px Arial"). - * @param {String} [color] The color to draw the text in. Any valid value for the CSS color attribute is acceptable (ex. - * "#F00", "red", or "#FF0000"). - * @protected - */ - p.initialize = function(text, font, color) { - this.DisplayObject_initialize(); - this.text = text; - this.font = font; - this.color = color; - }; +// public methods: /** * Returns true or false indicating whether the display object would be visible if drawn to a canvas. * This does not account for whether it would be visible within the boundaries of the stage. @@ -8793,13 +8655,6 @@ var p = Text.prototype = new createjs.DisplayObject(); return !!(this.visible && this.alpha > 0 && this.scaleX != 0 && this.scaleY != 0 && hasContent); }; - /** - * @property DisplayObject_draw - * @private - * @type Function - **/ - p.DisplayObject_draw = p.draw; - /** * Draws the Text into the specified context ignoring its visible, alpha, shadow, and transform. * Returns true if the draw was handled (useful for overriding functionality). @@ -8827,7 +8682,7 @@ var p = Text.prototype = new createjs.DisplayObject(); * @return {Number} The measured, untransformed width of the text. **/ p.getMeasuredWidth = function() { - return this._prepContext(Text._workingContext).measureText(this.text).width; + return this._getMeasuredWidth(this.text); }; /** @@ -8838,7 +8693,7 @@ var p = Text.prototype = new createjs.DisplayObject(); * based on the measured width of a "M" character multiplied by 1.2, which approximates em for most fonts. **/ p.getMeasuredLineHeight = function() { - return this._prepContext(Text._workingContext).measureText("M").width*1.2; + return this._getMeasuredWidth("M")*1.2; }; /** @@ -8851,13 +8706,6 @@ var p = Text.prototype = new createjs.DisplayObject(); p.getMeasuredHeight = function() { return this._drawText(null,{}).height; }; - - /** - * @property DisplayObject_getBounds - * @type Function - * @protected - **/ - p.DisplayObject_getBounds = p.getBounds; /** * Docced in superclass. @@ -8871,7 +8719,22 @@ var p = Text.prototype = new createjs.DisplayObject(); var x = w * Text.H_OFFSETS[this.textAlign||"left"]; var lineHeight = this.lineHeight||this.getMeasuredLineHeight(); var y = lineHeight * Text.V_OFFSETS[this.textBaseline||"top"]; - return this._rectangle.initialize(x, y, w, o.height); + return this._rectangle.setValues(x, y, w, o.height); + }; + + /** + * Returns an object with width, height, and lines properties. The width and height are the visual width and height + * of the drawn text. The lines property contains an array of strings, one for + * each line of text that will be drawn, accounting for line breaks and wrapping. These strings have trailing + * whitespace removed. + * @method getMetrics + * @return {Object} An object with width, height, and lines properties. + **/ + p.getMetrics = function() { + var o = {lines:[]}; + o.lineHeight = this.lineHeight || this.getMeasuredLineHeight(); + o.vOffset = o.lineHeight * Text.V_OFFSETS[this.textBaseline||"top"]; + return this._drawText(null, o, o.lines); }; /** @@ -8880,9 +8743,7 @@ var p = Text.prototype = new createjs.DisplayObject(); * @return {Text} a clone of the Text instance. **/ p.clone = function() { - var o = new Text(this.text, this.font, this.color); - this.cloneProps(o); - return o; + return this._cloneProps(new Text(this.text, this.font, this.color)); }; /** @@ -8894,28 +8755,23 @@ var p = Text.prototype = new createjs.DisplayObject(); return "[Text (text="+ (this.text.length > 20 ? this.text.substr(0, 17)+"..." : this.text) +")]"; }; + // private methods: - /** - * @property DisplayObject_cloneProps - * @private - * @type Function - **/ - p.DisplayObject_cloneProps = p.cloneProps; - - /** - * @method cloneProps + * @method _cloneProps * @param {Text} o * @protected + * @return {Text} o **/ - p.cloneProps = function(o) { - this.DisplayObject_cloneProps(o); + p._cloneProps = function(o) { + this.DisplayObject__cloneProps(o); o.textAlign = this.textAlign; o.textBaseline = this.textBaseline; o.maxWidth = this.maxWidth; o.outline = this.outline; o.lineHeight = this.lineHeight; o.lineWidth = this.lineWidth; + return o; }; /** @@ -8925,7 +8781,7 @@ var p = Text.prototype = new createjs.DisplayObject(); * @protected **/ p._prepContext = function(ctx) { - ctx.font = this.font; + ctx.font = this.font||"10px sans-serif"; ctx.textAlign = this.textAlign||"left"; ctx.textBaseline = this.textBaseline||"top"; return ctx; @@ -8936,18 +8792,23 @@ var p = Text.prototype = new createjs.DisplayObject(); * @method _drawText * @param {CanvasRenderingContext2D} ctx * @param {Object} o + * @param {Array} lines * @return {Object} * @protected **/ - p._drawText = function(ctx, o) { + p._drawText = function(ctx, o, lines) { var paint = !!ctx; - if (!paint) { ctx = this._prepContext(Text._workingContext); } + if (!paint) { + ctx = Text._workingContext; + ctx.save(); + this._prepContext(ctx); + } var lineHeight = this.lineHeight||this.getMeasuredLineHeight(); var maxW = 0, count = 0; - var lines = String(this.text).split(/(?:\r\n|\r|\n)/); - for (var i=0, l=lines.length; i this.lineWidth) { @@ -8961,6 +8822,7 @@ var p = Text.prototype = new createjs.DisplayObject(); var wordW = ctx.measureText(words[j] + words[j+1]).width; if (w + wordW > this.lineWidth) { if (paint) { this._drawTextLine(ctx, str, count*lineHeight); } + if (lines) { lines.push(str); } if (w > maxW) { maxW = w; } str = words[j+1]; w = ctx.measureText(str).width; @@ -8973,16 +8835,17 @@ var p = Text.prototype = new createjs.DisplayObject(); } if (paint) { this._drawTextLine(ctx, str, count*lineHeight); } + if (lines) { lines.push(str); } if (o && w == null) { w = ctx.measureText(str).width; } if (w > maxW) { maxW = w; } count++; } if (o) { - o.count = count; o.width = maxW; o.height = count*lineHeight; } + if (!paint) { ctx.restore(); } return o; }; @@ -8995,61 +8858,130 @@ var p = Text.prototype = new createjs.DisplayObject(); **/ p._drawTextLine = function(ctx, text, y) { // Chrome 17 will fail to draw the text if the last param is included but null, so we feed it a large value instead: - if (this.outline) { ctx.strokeText(text, 0, y, this.maxWidth||0xFFFF); } - else { ctx.fillText(text, 0, y, this.maxWidth||0xFFFF); } - + if (this.outline) { ctx.strokeText(text, 0, y, this.maxWidth||0xFFFF); } + else { ctx.fillText(text, 0, y, this.maxWidth||0xFFFF); } + }; + + + /** + * @method _getMeasuredWidth + * @param {String} text + * @protected + **/ + p._getMeasuredWidth = function(text) { + var ctx = Text._workingContext; + ctx.save(); + var w = this._prepContext(ctx).measureText(text).width; + ctx.restore(); + return w; }; -createjs.Text = Text; + + createjs.Text = createjs.promote(Text, "DisplayObject"); }()); -/* -* BitmapText -* Visit http://createjs.com/ for documentation, updates and examples. -* -* Copyright (c) 2010 gskinner.com, inc. -* -* Permission is hereby granted, free of charge, to any person -* obtaining a copy of this software and associated documentation -* files (the "Software"), to deal in the Software without -* restriction, including without limitation the rights to use, -* copy, modify, merge, publish, distribute, sublicense, and/or sell -* copies of the Software, and to permit persons to whom the -* Software is furnished to do so, subject to the following -* conditions: -* -* The above copyright notice and this permission notice shall be -* included in all copies or substantial portions of the Software. -* -* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES -* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT -* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR -* OTHER DEALINGS IN THE SOFTWARE. -*/ + +//############################################################################## +// BitmapText.js +//############################################################################## this.createjs = this.createjs || {}; (function () { "use strict"; -/** - * Displays text using bitmap glyphs defined in a sprite sheet. Multi-line text is supported - * using new line characters, but automatic wrapping is not supported. See the - * {{#crossLink "BitmapText/spriteSheet:attribute"}}{{/crossLink}} - * property for more information on defining glyphs. - * @class BitmapText - * @extends DisplayObject - * @param {String} [text=""] The text to display. - * @param {SpriteSheet} [spriteSheet=null] The spritesheet that defines the character glyphs. - * @constructor - **/ -function BitmapText(text, spriteSheet) { - this.initialize(text, spriteSheet); -} -var p = BitmapText.prototype = new createjs.Container(); + +// constructor: + /** + * Displays text using bitmap glyphs defined in a sprite sheet. Multi-line text is supported + * using new line characters, but automatic wrapping is not supported. See the + * {{#crossLink "BitmapText/spriteSheet:property"}}{{/crossLink}} + * property for more information on defining glyphs. + * + * Important: BitmapText extends Container, but is not designed to be used as one. + * As such, methods like addChild and removeChild are disabled. + * @class BitmapText + * @extends DisplayObject + * @param {String} [text=""] The text to display. + * @param {SpriteSheet} [spriteSheet=null] The spritesheet that defines the character glyphs. + * @constructor + **/ + function BitmapText(text, spriteSheet) { + this.Container_constructor(); + + + // public properties: + /** + * The text to display. + * @property text + * @type String + * @default "" + **/ + this.text = text||""; + + /** + * A SpriteSheet instance that defines the glyphs for this bitmap text. Each glyph/character + * should have a single frame animation defined in the sprite sheet named the same as + * corresponding character. For example, the following animation definition: + * + * "A": {frames: [0]} + * + * would indicate that the frame at index 0 of the spritesheet should be drawn for the "A" character. The short form + * is also acceptable: + * + * "A": 0 + * + * Note that if a character in the text is not found in the sprite sheet, it will also + * try to use the alternate case (upper or lower). + * + * See SpriteSheet for more information on defining sprite sheet data. + * @property spriteSheet + * @type String + * @default null + **/ + this.spriteSheet = spriteSheet; + + /** + * The height of each line of text. If 0, then it will use a line height calculated + * by checking for the height of the "1", "T", or "L" character (in that order). If + * those characters are not defined, it will use the height of the first frame of the + * sprite sheet. + * @property lineHeight + * @type Number + * @default 0 + **/ + this.lineHeight = 0; + + /** + * This spacing (in pixels) will be added after each character in the output. + * @property letterSpacing + * @type Number + * @default 0 + **/ + this.letterSpacing = 0; + + /** + * If a space character is not defined in the sprite sheet, then empty pixels equal to + * spaceWidth will be inserted instead. If 0, then it will use a value calculated + * by checking for the width of the "1", "l", "E", or "A" character (in that order). If + * those characters are not defined, it will use the width of the first frame of the + * sprite sheet. + * @property spaceWidth + * @type Number + * @default 0 + **/ + this.spaceWidth = 0; + + + // private properties: + /** + * @property _oldProps + * @type Object + * @protected + **/ + this._oldProps = {text:0,spriteSheet:0,lineHeight:0,letterSpacing:0,spaceWidth:0}; + } + var p = createjs.extend(BitmapText, createjs.Container); + // static properties: /** @@ -9071,109 +9003,8 @@ var p = BitmapText.prototype = new createjs.Container(); */ BitmapText._spritePool = []; -// events: - -// public properties: - /** - * The text to display. - * @property text - * @type String - * @default "" - **/ - p.text = ""; - - /** - * A SpriteSheet instance that defines the glyphs for this bitmap text. Each glyph/character - * should have a single frame animation defined in the sprite sheet named the same as - * corresponding character. For example, the following animation definition: - * - * "A": {frames: [0]} - * - * would indicate that the frame at index 0 of the spritesheet should be drawn for the "A" character. The short form - * is also acceptable: - * - * "A": 0 - * - * Note that if a character in the text is not found in the sprite sheet, it will also - * try to use the alternate case (upper or lower). - * - * See SpriteSheet for more information on defining sprite sheet data. - * @property spriteSheet - * @type String - * @default null - **/ - p.spriteSheet = null; - - /** - * The height of each line of text. If 0, then it will use a line height calculated - * by checking for the height of the "1", "T", or "L" character (in that order). If - * those characters are not defined, it will use the height of the first frame of the - * sprite sheet. - * @property lineHeight - * @type Number - * @default 0 - **/ - p.lineHeight = 0; - - /** - * This spacing (in pixels) will be added after each character in the output. - * @property letterSpacing - * @type Number - * @default 0 - **/ - p.letterSpacing = 0; - - /** - * If a space character is not defined in the sprite sheet, then empty pixels equal to - * spaceWidth will be inserted instead. If 0, then it will use a value calculated - * by checking for the width of the "1", "l", "E", or "A" character (in that order). If - * those characters are not defined, it will use the width of the first frame of the - * sprite sheet. - * @property spaceWidth - * @type Number - * @default 0 - **/ - p.spaceWidth = 0; - -// private properties: - /** - * @property _oldProps - * @type Object - * @protected - **/ - p._oldProps = null; - -// constructor: - /** - * @property Container_initialize - * @type Function - * @protected - **/ - p.Container_initialize = p.initialize; - - /** - * Initialization method. - * @method initialize - * @param {String} [text=""] The text to display. - * @param {SpriteSheet} [spriteSheet=null] The spritesheet that defines the character glyphs. - * @protected - **/ - p.initialize = function (text, spriteSheet) { - this.Container_initialize(); - - this.text = text; - this.spriteSheet = spriteSheet; - this._oldProps = {text:0,spriteSheet:0,lineHeight:0,letterSpacing:0,spaceWidth:0}; - }; // public methods: - /** - * @property DisplayObject_draw - * @type Function - * @protected - **/ - p.Container_draw = p.draw; - /** * Docced in superclass. **/ @@ -9183,18 +9014,10 @@ var p = BitmapText.prototype = new createjs.Container(); this.Container_draw(ctx, ignoreCache); }; - /** - * @property Container_getBounds - * @type Function - * @protected - **/ - p.Container_getBounds = p.getBounds; - /** * Docced in superclass. **/ p.getBounds = function() { - // getBounds is somewhat expensive this._updateText(); return this.Container_getBounds(); }; @@ -9211,9 +9034,48 @@ var p = BitmapText.prototype = new createjs.Container(); return !!(this.visible && this.alpha > 0 && this.scaleX != 0 && this.scaleY != 0 && hasContent); }; - // TODO: should probably disable addChild / addChildAt + p.clone = function() { + return this._cloneProps(new BitmapText(this.text, this.spriteSheet)); + }; + + /** + * Disabled in BitmapText. + * @method addChild + **/ + /** + * Disabled in BitmapText. + * @method addChildAt + **/ + /** + * Disabled in BitmapText. + * @method removeChild + **/ + /** + * Disabled in BitmapText. + * @method removeChildAt + **/ + /** + * Disabled in BitmapText. + * @method removeAllChildren + **/ + p.addChild = p.addChildAt = p.removeChild = p.removeChildAt = p.removeAllChildren = function() {}; + // private methods: + /** + * @method _cloneProps + * @param {BitmapText} o + * @return {BitmapText} o + * @protected + **/ + p._cloneProps = function(o) { + this.DisplayObject__cloneProps(o); + o.lineHeight = this.lineHeight; + o.letterSpacing = this.letterSpacing; + o.spaceWidth = this.spaceWidth; + return o; + }; + /** * @method _getFrameIndex * @param {String} character @@ -9301,7 +9163,8 @@ var p = BitmapText.prototype = new createjs.Container(); if (childIndex < numKids) { sprite = kids[childIndex]; } else { - sprite = this.addChild( pool.length ? pool.pop() : new createjs.Sprite() ); + kids.push(sprite = pool.length ? pool.pop() : new createjs.Sprite()); + sprite.parent = this; numKids++; } sprite.spriteSheet = ss; @@ -9312,68 +9175,45 @@ var p = BitmapText.prototype = new createjs.Container(); x += sprite.getBounds().width + this.letterSpacing; } - while (numKids > childIndex) { pool.push(sprite = kids.pop()); sprite.parent = null; } // faster than removeChild. + while (numKids > childIndex) { pool.push(sprite = kids.pop()); sprite.parent = null; numKids--; } // faster than removeChild. if (pool.length > BitmapText.maxPoolSize) { pool.length = BitmapText.maxPoolSize; } }; - createjs.BitmapText = BitmapText; -}());/* -* SpriteSheetUtils -* Visit http://createjs.com/ for documentation, updates and examples. -* -* Copyright (c) 2010 gskinner.com, inc. -* -* Permission is hereby granted, free of charge, to any person -* obtaining a copy of this software and associated documentation -* files (the "Software"), to deal in the Software without -* restriction, including without limitation the rights to use, -* copy, modify, merge, publish, distribute, sublicense, and/or sell -* copies of the Software, and to permit persons to whom the -* Software is furnished to do so, subject to the following -* conditions: -* -* The above copyright notice and this permission notice shall be -* included in all copies or substantial portions of the Software. -* -* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES -* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT -* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR -* OTHER DEALINGS IN THE SOFTWARE. -*/ -/** - * @module EaselJS - */ + createjs.BitmapText = createjs.promote(BitmapText, "Container"); +}()); + +//############################################################################## +// SpriteSheetUtils.js +//############################################################################## -// namespace: this.createjs = this.createjs||{}; (function() { "use strict"; + + // constructor: -/** - * The SpriteSheetUtils class is a collection of static methods for working with {{#crossLink "SpriteSheet"}}{{/crossLink}}s. - * A sprite sheet is a series of images (usually animation frames) combined into a single image on a regular grid. For - * example, an animation consisting of 8 100x100 images could be combined into a 400x200 sprite sheet (4 frames across - * by 2 high). The SpriteSheetUtils class uses a static interface and should not be instantiated. - * @class SpriteSheetUtils - * @static - **/ -var SpriteSheetUtils = function() { - throw "SpriteSheetUtils cannot be instantiated"; -}; + /** + * The SpriteSheetUtils class is a collection of static methods for working with {{#crossLink "SpriteSheet"}}{{/crossLink}}s. + * A sprite sheet is a series of images (usually animation frames) combined into a single image on a regular grid. For + * example, an animation consisting of 8 100x100 images could be combined into a 400x200 sprite sheet (4 frames across + * by 2 high). The SpriteSheetUtils class uses a static interface and should not be instantiated. + * @class SpriteSheetUtils + * @static + **/ + function SpriteSheetUtils() { + throw "SpriteSheetUtils cannot be instantiated"; + } + +// private static properties: /** * @property _workingCanvas * @static * @type HTMLCanvasElement | Object * @protected */ - /** * @property _workingContext * @static @@ -9387,7 +9227,6 @@ var SpriteSheetUtils = function() { canvas.width = canvas.height = 1; } - // public static methods: /** @@ -9541,71 +9380,162 @@ var SpriteSheetUtils = function() { }; -createjs.SpriteSheetUtils = SpriteSheetUtils; + createjs.SpriteSheetUtils = SpriteSheetUtils; }()); -/* -* SpriteSheetBuilder -* Visit http://createjs.com/ for documentation, updates and examples. -* -* Copyright (c) 2010 gskinner.com, inc. -* -* Permission is hereby granted, free of charge, to any person -* obtaining a copy of this software and associated documentation -* files (the "Software"), to deal in the Software without -* restriction, including without limitation the rights to use, -* copy, modify, merge, publish, distribute, sublicense, and/or sell -* copies of the Software, and to permit persons to whom the -* Software is furnished to do so, subject to the following -* conditions: -* -* The above copyright notice and this permission notice shall be -* included in all copies or substantial portions of the Software. -* -* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES -* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT -* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR -* OTHER DEALINGS IN THE SOFTWARE. -*/ -/** - * @module EaselJS - */ +//############################################################################## +// SpriteSheetBuilder.js +//############################################################################## -// namespace: this.createjs = this.createjs||{}; (function() { "use strict"; -/** - * The SpriteSheetBuilder allows you to generate sprite sheets at run time from any display object. This can allow - * you to maintain your assets as vector graphics (for low file size), and render them at run time as sprite sheets - * for better performance. - * - * Sprite sheets can be built either synchronously, or asynchronously, so that large sprite sheets can be generated - * without locking the UI. - * - * Note that the "images" used in the generated sprite sheet are actually canvas elements, and that they will be sized - * to the nearest power of 2 up to the value of maxWidth or maxHeight. - * @class SpriteSheetBuilder - * @extends EventDispatcher - * @constructor - **/ -var SpriteSheetBuilder = function() { - this.initialize(); -}; -var p = SpriteSheetBuilder.prototype = new createjs.EventDispatcher; + +// constructor: + /** + * The SpriteSheetBuilder allows you to generate sprite sheets at run time from any display object. This can allow + * you to maintain your assets as vector graphics (for low file size), and render them at run time as sprite sheets + * for better performance. + * + * Sprite sheets can be built either synchronously, or asynchronously, so that large sprite sheets can be generated + * without locking the UI. + * + * Note that the "images" used in the generated sprite sheet are actually canvas elements, and that they will be sized + * to the nearest power of 2 up to the value of maxWidth or maxHeight. + * @class SpriteSheetBuilder + * @extends EventDispatcher + * @constructor + **/ + function SpriteSheetBuilder() { + this.EventDispatcher_constructor(); + + // public properties: + /** + * The maximum width for the images (not individual frames) in the generated sprite sheet. It is recommended to use + * a power of 2 for this value (ex. 1024, 2048, 4096). If the frames cannot all fit within the max dimensions, then + * additional images will be created as needed. + * @property maxWidth + * @type Number + * @default 2048 + */ + this.maxWidth = 2048; + + /** + * The maximum height for the images (not individual frames) in the generated sprite sheet. It is recommended to use + * a power of 2 for this value (ex. 1024, 2048, 4096). If the frames cannot all fit within the max dimensions, then + * additional images will be created as needed. + * @property maxHeight + * @type Number + * @default 2048 + **/ + this.maxHeight = 2048; + + /** + * The sprite sheet that was generated. This will be null before a build is completed successfully. + * @property spriteSheet + * @type SpriteSheet + **/ + this.spriteSheet = null; + + /** + * The scale to apply when drawing all frames to the sprite sheet. This is multiplied against any scale specified + * in the addFrame call. This can be used, for example, to generate a sprite sheet at run time that is tailored to + * the a specific device resolution (ex. tablet vs mobile). + * @property scale + * @type Number + * @default 1 + **/ + this.scale = 1; + + /** + * The padding to use between frames. This is helpful to preserve antialiasing on drawn vector content. + * @property padding + * @type Number + * @default 1 + **/ + this.padding = 1; + + /** + * A number from 0.01 to 0.99 that indicates what percentage of time the builder can use. This can be + * thought of as the number of seconds per second the builder will use. For example, with a timeSlice value of 0.3, + * the builder will run 20 times per second, using approximately 15ms per build (30% of available time, or 0.3s per second). + * Defaults to 0.3. + * @property timeSlice + * @type Number + * @default 0.3 + **/ + this.timeSlice = 0.3; + + /** + * A value between 0 and 1 that indicates the progress of a build, or -1 if a build has not + * been initiated. + * @property progress + * @type Number + * @default -1 + * @readonly + **/ + this.progress = -1; + + + // private properties: + /** + * @property _frames + * @protected + * @type Array + **/ + this._frames = []; + + /** + * @property _animations + * @protected + * @type Array + **/ + this._animations = {}; + + /** + * @property _data + * @protected + * @type Array + **/ + this._data = null; + + /** + * @property _nextFrameIndex + * @protected + * @type Number + **/ + this._nextFrameIndex = 0; + + /** + * @property _index + * @protected + * @type Number + **/ + this._index = 0; + + /** + * @property _timerID + * @protected + * @type Number + **/ + this._timerID = null; + + /** + * @property _scale + * @protected + * @type Number + **/ + this._scale = 1; + } + var p = createjs.extend(SpriteSheetBuilder, createjs.EventDispatcher); // constants: SpriteSheetBuilder.ERR_DIMENSIONS = "frame dimensions exceed max spritesheet dimensions"; SpriteSheetBuilder.ERR_RUNNING = "a build is already running"; // events: - /** * Dispatched when a build completes. * @event complete @@ -9616,161 +9546,15 @@ var p = SpriteSheetBuilder.prototype = new createjs.EventDispatcher; /** * Dispatched when an asynchronous build has progress. - * @event complete + * @event progress * @param {Object} target The object that dispatched the event. * @param {String} type The event type. * @param {Number} progress The current progress value (0-1). * @since 0.6.0 */ -// public properties: - - /** - * The maximum width for the images (not individual frames) in the generated sprite sheet. It is recommended to use - * a power of 2 for this value (ex. 1024, 2048, 4096). If the frames cannot all fit within the max dimensions, then - * additional images will be created as needed. - * @property maxWidth - * @type Number - * @default 2048 - */ - p.maxWidth = 2048; - - /** - * The maximum height for the images (not individual frames) in the generated sprite sheet. It is recommended to use - * a power of 2 for this value (ex. 1024, 2048, 4096). If the frames cannot all fit within the max dimensions, then - * additional images will be created as needed. - * @property maxHeight - * @type Number - * @default 2048 - **/ - p.maxHeight = 2048; - - /** - * The sprite sheet that was generated. This will be null before a build is completed successfully. - * @property spriteSheet - * @type SpriteSheet - **/ - p.spriteSheet = null; - - /** - * The scale to apply when drawing all frames to the sprite sheet. This is multiplied against any scale specified - * in the addFrame call. This can be used, for example, to generate a sprite sheet at run time that is tailored to - * the a specific device resolution (ex. tablet vs mobile). - * @property scale - * @type Number - * @default 1 - **/ - p.scale = 1; - - /** - * The padding to use between frames. This is helpful to preserve antialiasing on drawn vector content. - * @property padding - * @type Number - * @default 1 - **/ - p.padding = 1; - - /** - * A number from 0.01 to 0.99 that indicates what percentage of time the builder can use. This can be - * thought of as the number of seconds per second the builder will use. For example, with a timeSlice value of 0.3, - * the builder will run 20 times per second, using approximately 15ms per build (30% of available time, or 0.3s per second). - * Defaults to 0.3. - * @property timeSlice - * @type Number - * @default 0.3 - **/ - p.timeSlice = 0.3; - - /** - * A value between 0 and 1 that indicates the progress of a build, or -1 if a build has not - * been initiated. - * @property progress - * @type Number - * @default -1 - * @readonly - **/ - p.progress = -1; - - // TODO: deprecated. - /** - * REMOVED. Use {{#crossLink "EventDispatcher/addEventListener"}}{{/crossLink}} and the {{#crossLink "SpriteSheetBuilder/complete:event"}}{{/crossLink}} - * event. - * @property onComplete - * @type Function - * @deprecated Use addEventListener and the "complete" event. - */ - /** - * REMOVED. Use {{#crossLink "EventDispatcher/addEventListener"}}{{/crossLink}} and the {{#crossLink "SpriteSheetBuilder/progress:event"}}{{/crossLink}} - * event. - * @property onProgress - * @type Function - * @deprecated Use addEventListener and the "progress" event. - */ - -// private properties: - - /** - * @property _frames - * @protected - * @type Array - **/ - p._frames = null; - - /** - * @property _animations - * @protected - * @type Array - **/ - p._animations = null; - - /** - * @property _data - * @protected - * @type Array - **/ - p._data = null; - - /** - * @property _nextFrameIndex - * @protected - * @type Number - **/ - p._nextFrameIndex = 0; - - /** - * @property _index - * @protected - * @type Number - **/ - p._index = 0; - - /** - * @property _timerID - * @protected - * @type Number - **/ - p._timerID = null; - - /** - * @property _scale - * @protected - * @type Number - **/ - p._scale = 1; - -// constructor: - /** - * Initialization method. - * @method initialize - * @protected - **/ - p.initialize = function() { - this._frames = []; - this._animations = {}; - }; // public methods: - /** * Adds a frame to the {{#crossLink "SpriteSheet"}}{{/crossLink}}. Note that the frame will not be drawn until you * call {{#crossLink "SpriteSheetBuilder/build"}}{{/crossLink}} method. The optional setup params allow you to have @@ -9922,6 +9706,7 @@ var p = SpriteSheetBuilder.prototype = new createjs.EventDispatcher; return "[SpriteSheetBuilder]"; }; + // private methods: /** * @method _startBuild @@ -9963,7 +9748,6 @@ var p = SpriteSheetBuilder.prototype = new createjs.EventDispatcher; } }; - /** * @method _setupMovieClipFrame * @protected @@ -10086,132 +9870,83 @@ var p = SpriteSheetBuilder.prototype = new createjs.EventDispatcher; return (++this._index) < this._frames.length; }; -createjs.SpriteSheetBuilder = SpriteSheetBuilder; + + createjs.SpriteSheetBuilder = createjs.promote(SpriteSheetBuilder, "EventDispatcher"); }()); -/* -* DOMElement -* Visit http://createjs.com/ for documentation, updates and examples. -* -* Copyright (c) 2010 gskinner.com, inc. -* -* Permission is hereby granted, free of charge, to any person -* obtaining a copy of this software and associated documentation -* files (the "Software"), to deal in the Software without -* restriction, including without limitation the rights to use, -* copy, modify, merge, publish, distribute, sublicense, and/or sell -* copies of the Software, and to permit persons to whom the -* Software is furnished to do so, subject to the following -* conditions: -* -* The above copyright notice and this permission notice shall be -* included in all copies or substantial portions of the Software. -* -* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES -* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT -* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR -* OTHER DEALINGS IN THE SOFTWARE. -*/ -/** - * @module EaselJS - */ +//############################################################################## +// DOMElement.js +//############################################################################## -// namespace: this.createjs = this.createjs||{}; (function() { "use strict"; -// TODO: fix problems with rotation. -// TODO: exclude from getObjectsUnderPoint -/** - * This class is still experimental, and more advanced use is likely to be buggy. Please report bugs. - * - * A DOMElement allows you to associate a HTMLElement with the display list. It will be transformed - * within the DOM as though it is child of the {{#crossLink "Container"}}{{/crossLink}} it is added to. However, it is - * not rendered to canvas, and as such will retain whatever z-index it has relative to the canvas (ie. it will be - * drawn in front of or behind the canvas). - * - * The position of a DOMElement is relative to their parent node in the DOM. It is recommended that - * the DOM Object be added to a div that also contains the canvas so that they share the same position - * on the page. - * - * DOMElement is useful for positioning HTML elements over top of canvas content, and for elements - * that you want to display outside the bounds of the canvas. For example, a tooltip with rich HTML - * content. - * - *

Mouse Interaction

- * - * DOMElement instances are not full EaselJS display objects, and do not participate in EaselJS mouse - * events or support methods like hitTest. To get mouse events from a DOMElement, you must instead add handlers to - * the htmlElement (note, this does not support EventDispatcher) - * - * var domElement = new createjs.DOMElement(htmlElement); - * domElement.htmlElement.onclick = function() { - * console.log("clicked"); - * } - * - * @class DOMElement - * @extends DisplayObject - * @constructor - * @param {HTMLElement} htmlElement A reference or id for the DOM element to manage. - */ -var DOMElement = function(htmlElement) { - this.initialize(htmlElement); -}; -var p = DOMElement.prototype = new createjs.DisplayObject(); - -// public properties: - /** - * The DOM object to manage. - * @property htmlElement - * @type HTMLElement - */ - p.htmlElement = null; - -// private properties: - /** - * @property _oldMtx - * @type Matrix2D - * @protected - */ - p._oldMtx = null; - - /** - * @property _visible - * @type Boolean - * @protected - */ - p._visible = false; // constructor: /** - * @property DisplayObject_initialize - * @type Function - * @private - */ - p.DisplayObject_initialize = p.initialize; - - /** - * Initialization method. - * @method initialize + * This class is still experimental, and more advanced use is likely to be buggy. Please report bugs. + * + * A DOMElement allows you to associate a HTMLElement with the display list. It will be transformed + * within the DOM as though it is child of the {{#crossLink "Container"}}{{/crossLink}} it is added to. However, it is + * not rendered to canvas, and as such will retain whatever z-index it has relative to the canvas (ie. it will be + * drawn in front of or behind the canvas). + * + * The position of a DOMElement is relative to their parent node in the DOM. It is recommended that + * the DOM Object be added to a div that also contains the canvas so that they share the same position + * on the page. + * + * DOMElement is useful for positioning HTML elements over top of canvas content, and for elements + * that you want to display outside the bounds of the canvas. For example, a tooltip with rich HTML + * content. + * + *

Mouse Interaction

+ * + * DOMElement instances are not full EaselJS display objects, and do not participate in EaselJS mouse + * events or support methods like hitTest. To get mouse events from a DOMElement, you must instead add handlers to + * the htmlElement (note, this does not support EventDispatcher) + * + * var domElement = new createjs.DOMElement(htmlElement); + * domElement.htmlElement.onclick = function() { + * console.log("clicked"); + * } + * + * @class DOMElement + * @extends DisplayObject + * @constructor * @param {HTMLElement} htmlElement A reference or id for the DOM element to manage. - * @protected - */ - p.initialize = function(htmlElement) { + */ + function DOMElement(htmlElement) { + this.DisplayObject_constructor(); + if (typeof(htmlElement)=="string") { htmlElement = document.getElementById(htmlElement); } - this.DisplayObject_initialize(); this.mouseEnabled = false; - this.htmlElement = htmlElement; + var style = htmlElement.style; - // this relies on the _tick method because draw isn't called if a parent is not visible. style.position = "absolute"; style.transformOrigin = style.WebkitTransformOrigin = style.msTransformOrigin = style.MozTransformOrigin = style.OTransformOrigin = "0% 0%"; - }; + + + // public properties: + /** + * The DOM object to manage. + * @property htmlElement + * @type HTMLElement + */ + this.htmlElement = htmlElement; + + + // private properties: + /** + * @property _oldMtx + * @type Matrix2D + * @protected + */ + this._oldProps = null; + } + var p = createjs.extend(DOMElement, createjs.DisplayObject); + // public methods: /** @@ -10237,7 +9972,7 @@ var p = DOMElement.prototype = new createjs.DisplayObject(); * @return {Boolean} */ p.draw = function(ctx, ignoreCache) { - // this relies on the _tick method because draw isn't called if a parent is not visible. + // this relies on the _tick method because draw isn't called if the parent is not visible. // the actual update happens in _handleDrawEnd return true; }; @@ -10332,23 +10067,16 @@ var p = DOMElement.prototype = new createjs.DisplayObject(); // private methods: - /** - * @property DisplayObject__tick - * @type Function - * @protected - */ - p.DisplayObject__tick = p._tick; - /** * @method _tick - * @param {Array} params Parameters to pass onto the DisplayObject {{#crossLink "DisplayObject/tick"}}{{/crossLink}} + * @param {Object} evtObj An event object that will be dispatched to all tick listeners. This object is reused between dispatchers to reduce construction & GC costs. * function. * @protected */ - p._tick = function(params) { + p._tick = function(evtObj) { var stage = this.getStage(); stage&&stage.on("drawend", this._handleDrawEnd, this, true); - this.DisplayObject__tick(params); + this.DisplayObject__tick(evtObj); }; /** @@ -10361,107 +10089,77 @@ var p = DOMElement.prototype = new createjs.DisplayObject(); if (!o) { return; } var style = o.style; - var mtx = this.getConcatenatedMatrix(this._matrix); + var props = this.getConcatenatedDisplayProps(this._props), mtx = props.matrix; - var visibility = mtx.visible ? "visible" : "hidden"; + var visibility = props.visible ? "visible" : "hidden"; if (visibility != style.visibility) { style.visibility = visibility; } - if (!mtx.visible) { return; } + if (!props.visible) { return; } - var oMtx = this._oldMtx; + var oldProps = this._oldProps, oldMtx = oldProps&&oldProps.matrix; var n = 10000; // precision - if (!oMtx || oMtx.alpha != mtx.alpha) { - style.opacity = ""+(mtx.alpha*n|0)/n; - if (oMtx) { oMtx.alpha = mtx.alpha; } - } - if (!oMtx || oMtx.tx != mtx.tx || oMtx.ty != mtx.ty || oMtx.a != mtx.a || oMtx.b != mtx.b || oMtx.c != mtx.c || oMtx.d != mtx.d) { + + if (!oldMtx || !oldMtx.equals(mtx)) { var str = "matrix(" + (mtx.a*n|0)/n +","+ (mtx.b*n|0)/n +","+ (mtx.c*n|0)/n +","+ (mtx.d*n|0)/n +","+ (mtx.tx+0.5|0); style.transform = style.WebkitTransform = style.OTransform = style.msTransform = str +","+ (mtx.ty+0.5|0) +")"; style.MozTransform = str +"px,"+ (mtx.ty+0.5|0) +"px)"; - this._oldMtx = oMtx ? oMtx.copy(mtx) : mtx.clone(); + if (!oldProps) { oldProps = this._oldProps = new createjs.DisplayProps(true, NaN); } + oldProps.matrix.copy(mtx); + } + + if (oldProps.alpha != props.alpha) { + style.opacity = ""+(props.alpha*n|0)/n; + oldProps.alpha = props.alpha; } }; -createjs.DOMElement = DOMElement; + + createjs.DOMElement = createjs.promote(DOMElement, "DisplayObject"); }()); -/* -* Filter -* Visit http://createjs.com/ for documentation, updates and examples. -* -* Copyright (c) 2010 gskinner.com, inc. -* -* Permission is hereby granted, free of charge, to any person -* obtaining a copy of this software and associated documentation -* files (the "Software"), to deal in the Software without -* restriction, including without limitation the rights to use, -* copy, modify, merge, publish, distribute, sublicense, and/or sell -* copies of the Software, and to permit persons to whom the -* Software is furnished to do so, subject to the following -* conditions: -* -* The above copyright notice and this permission notice shall be -* included in all copies or substantial portions of the Software. -* -* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES -* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT -* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR -* OTHER DEALINGS IN THE SOFTWARE. -*/ -/** - * @module EaselJS - */ +//############################################################################## +// Filter.js +//############################################################################## -// namespace: this.createjs = this.createjs||{}; (function() { "use strict"; -/** - * Base class that all filters should inherit from. Filters need to be applied to objects that have been cached using - * the {{#crossLink "DisplayObject/cache"}}{{/crossLink}} method. If an object changes, please cache it again, or use - * {{#crossLink "DisplayObject/updateCache"}}{{/crossLink}}. Note that the filters must be applied before caching. - * - *

Example

- * myInstance.filters = [ - * new createjs.ColorFilter(0, 0, 0, 1, 255, 0, 0), - * new createjs.BlurFilter(5, 5, 10) - * ]; - * myInstance.cache(0,0, 100, 100); - * - * Note that each filter can implement a {{#crossLink "Filter/getBounds"}}{{/crossLink}} method, which returns the - * margins that need to be applied in order to fully display the filter. For example, the {{#crossLink "BlurFilter"}}{{/crossLink}} - * will cause an object to feather outwards, resulting in a margin around the shape. - * - *

EaselJS Filters

- * EaselJS comes with a number of pre-built filters. Note that individual filters are not compiled into the minified - * version of EaselJS. To use them, you must include them manually in the HTML. - *
  • {{#crossLink "AlphaMapFilter"}}{{/crossLink}} : Map a greyscale image to the alpha channel of a display object
  • - *
  • {{#crossLink "AlphaMaskFilter"}}{{/crossLink}}: Map an image's alpha channel to the alpha channel of a display object
  • - *
  • {{#crossLink "BlurFilter"}}{{/crossLink}}: Apply vertical and horizontal blur to a display object
  • - *
  • {{#crossLink "ColorFilter"}}{{/crossLink}}: Color transform a display object
  • - *
  • {{#crossLink "ColorMatrixFilter"}}{{/crossLink}}: Transform an image using a {{#crossLink "ColorMatrix"}}{{/crossLink}}
  • - *
- * - * @class Filter - * @constructor - **/ -var Filter = function() { - this.initialize(); -}; -var p = Filter.prototype; // constructor: /** - * Initialization method. - * @method initialize - * @protected + * Base class that all filters should inherit from. Filters need to be applied to objects that have been cached using + * the {{#crossLink "DisplayObject/cache"}}{{/crossLink}} method. If an object changes, please cache it again, or use + * {{#crossLink "DisplayObject/updateCache"}}{{/crossLink}}. Note that the filters must be applied before caching. + * + *

Example

+ * + * myInstance.filters = [ + * new createjs.ColorFilter(0, 0, 0, 1, 255, 0, 0), + * new createjs.BlurFilter(5, 5, 10) + * ]; + * myInstance.cache(0,0, 100, 100); + * + * Note that each filter can implement a {{#crossLink "Filter/getBounds"}}{{/crossLink}} method, which returns the + * margins that need to be applied in order to fully display the filter. For example, the {{#crossLink "BlurFilter"}}{{/crossLink}} + * will cause an object to feather outwards, resulting in a margin around the shape. + * + *

EaselJS Filters

+ * EaselJS comes with a number of pre-built filters. Note that individual filters are not compiled into the minified + * version of EaselJS. To use them, you must include them manually in the HTML. + *
  • {{#crossLink "AlphaMapFilter"}}{{/crossLink}} : Map a greyscale image to the alpha channel of a display object
  • + *
  • {{#crossLink "AlphaMaskFilter"}}{{/crossLink}}: Map an image's alpha channel to the alpha channel of a display object
  • + *
  • {{#crossLink "BlurFilter"}}{{/crossLink}}: Apply vertical and horizontal blur to a display object
  • + *
  • {{#crossLink "ColorFilter"}}{{/crossLink}}: Color transform a display object
  • + *
  • {{#crossLink "ColorMatrixFilter"}}{{/crossLink}}: Transform an image using a {{#crossLink "ColorMatrix"}}{{/crossLink}}
  • + *
+ * + * @class Filter + * @constructor **/ - p.initialize = function() {} + function Filter() {} + var p = Filter.prototype; + // public methods: /** @@ -10488,7 +10186,22 @@ var p = Filter.prototype; * @param {Number} [targetY] The y position to draw the result to. Defaults to the value passed to y. * @return {Boolean} If the filter was applied successfully. **/ - p.applyFilter = function(ctx, x, y, width, height, targetCtx, targetX, targetY) {} + p.applyFilter = function(ctx, x, y, width, height, targetCtx, targetX, targetY) { + // this is the default behaviour because most filters access pixel data. It is overridden when not needed. + targetCtx = targetCtx || ctx; + if (targetX == null) { targetX = x; } + if (targetY == null) { targetY = y; } + try { + var imageData = ctx.getImageData(x, y, width, height); + } catch (e) { + return false; + } + if (this._applyFilter(imageData)) { + targetCtx.putImageData(imageData, targetX, targetY); + return true; + } + return false; + }; /** * Returns a string representation of this object. @@ -10507,120 +10220,109 @@ var p = Filter.prototype; p.clone = function() { return new Filter(); }; + +// private methods: + /** + * @method _applyFilter + * @param {ImageData} imageData Target ImageData instance. + * @return {Boolean} + **/ + p._applyFilter = function(imageData) { return true; }; -createjs.Filter = Filter; + + createjs.Filter = Filter; }()); -/* -* BlurFilter -* Visit http://createjs.com/ for documentation, updates and examples. -* -* Copyright (c) 2010 gskinner.com, inc. -* -* Permission is hereby granted, free of charge, to any person -* obtaining a copy of this software and associated documentation -* files (the "Software"), to deal in the Software without -* restriction, including without limitation the rights to use, -* copy, modify, merge, publish, distribute, sublicense, and/or sell -* copies of the Software, and to permit persons to whom the -* Software is furnished to do so, subject to the following -* conditions: -* -* The above copyright notice and this permission notice shall be -* included in all copies or substantial portions of the Software. -* -* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES -* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT -* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR -* OTHER DEALINGS IN THE SOFTWARE. -*/ -/** - * @module EaselJS - */ +//############################################################################## +// BlurFilter.js +//############################################################################## -// namespace: this.createjs = this.createjs||{}; (function() { "use strict"; -/** - * Applies a box blur to DisplayObjects. Note that this filter is fairly CPU intensive, particularly if the quality is - * set higher than 1. - * - *

Example

- * This example creates a red circle, and then applies a 5 pixel blur to it. It uses the {{#crossLink "Filter/getBounds"}}{{/crossLink}} - * method to account for the spread that the blur causes. - * - * var shape = new createjs.Shape().set({x:100,y:100}); - * shape.graphics.beginFill("#ff0000").drawCircle(0,0,50); - * - * var blurFilter = new createjs.BlurFilter(5, 5, 1); - * shape.filters = [blurFilter]; - * var bounds = blurFilter.getBounds(); - * - * shape.cache(-50+bounds.x, -50+bounds.y, 100+bounds.width, 100+bounds.height); - * - * See {{#crossLink "Filter"}}{{/crossLink}} for an more information on applying filters. - * @class BlurFilter - * @extends Filter - * @constructor - * @param {Number} [blurX=0] The horizontal blur radius in pixels. - * @param {Number} [blurY=0] The vertical blur radius in pixels. - * @param {Number} [quality=1] The number of blur iterations. - **/ -var BlurFilter = function( blurX, blurY, quality ) { - this.initialize( blurX, blurY, quality ); -}; -var p = BlurFilter.prototype = new createjs.Filter(); // constructor: - /** @ignore */ - p.initialize = function( blurX, blurY, quality ) { + /** + * Applies a box blur to DisplayObjects. Note that this filter is fairly CPU intensive, particularly if the quality is + * set higher than 1. + * + *

Example

+ * This example creates a red circle, and then applies a 5 pixel blur to it. It uses the {{#crossLink "Filter/getBounds"}}{{/crossLink}} + * method to account for the spread that the blur causes. + * + * var shape = new createjs.Shape().set({x:100,y:100}); + * shape.graphics.beginFill("#ff0000").drawCircle(0,0,50); + * + * var blurFilter = new createjs.BlurFilter(5, 5, 1); + * shape.filters = [blurFilter]; + * var bounds = blurFilter.getBounds(); + * + * shape.cache(-50+bounds.x, -50+bounds.y, 100+bounds.width, 100+bounds.height); + * + * See {{#crossLink "Filter"}}{{/crossLink}} for an more information on applying filters. + * @class BlurFilter + * @extends Filter + * @constructor + * @param {Number} [blurX=0] The horizontal blur radius in pixels. + * @param {Number} [blurY=0] The vertical blur radius in pixels. + * @param {Number} [quality=1] The number of blur iterations. + **/ + function BlurFilter( blurX, blurY, quality) { if ( isNaN(blurX) || blurX < 0 ) blurX = 0; - this.blurX = blurX | 0; if ( isNaN(blurY) || blurY < 0 ) blurY = 0; - this.blurY = blurY | 0; if ( isNaN(quality) || quality < 1 ) quality = 1; - this.quality = quality | 0; - }; - -// public properties: - - /** - * Horizontal blur radius in pixels - * @property blurX - * @default 0 - * @type Number - **/ - p.blurX = 0; - - /** - * Vertical blur radius in pixels - * @property blurY - * @default 0 - * @type Number - **/ - p.blurY = 0; - - /** - * Number of blur iterations. For example, a value of 1 will produce a rough blur. A value of 2 will produce a - * smoother blur, but take twice as long to run. - * @property quality - * @default 1 - * @type Number - **/ - p.quality = 1; + + + // public properties: + /** + * Horizontal blur radius in pixels + * @property blurX + * @default 0 + * @type Number + **/ + this.blurX = blurX | 0; - //TODO: There might be a better better way to place these two lookup tables: - p.mul_table = [ 1,171,205,293,57,373,79,137,241,27,391,357,41,19,283,265,497,469,443,421,25,191,365,349,335,161,155,149,9,278,269,261,505,245,475,231,449,437,213,415,405,395,193,377,369,361,353,345,169,331,325,319,313,307,301,37,145,285,281,69,271,267,263,259,509,501,493,243,479,118,465,459,113,446,55,435,429,423,209,413,51,403,199,393,97,3,379,375,371,367,363,359,355,351,347,43,85,337,333,165,327,323,5,317,157,311,77,305,303,75,297,294,73,289,287,71,141,279,277,275,68,135,67,133,33,262,260,129,511,507,503,499,495,491,61,121,481,477,237,235,467,232,115,457,227,451,7,445,221,439,218,433,215,427,425,211,419,417,207,411,409,203,202,401,399,396,197,49,389,387,385,383,95,189,47,187,93,185,23,183,91,181,45,179,89,177,11,175,87,173,345,343,341,339,337,21,167,83,331,329,327,163,81,323,321,319,159,79,315,313,39,155,309,307,153,305,303,151,75,299,149,37,295,147,73,291,145,289,287,143,285,71,141,281,35,279,139,69,275,137,273,17,271,135,269,267,133,265,33,263,131,261,130,259,129,257,1]; - - - p.shg_table = [0,9,10,11,9,12,10,11,12,9,13,13,10,9,13,13,14,14,14,14,10,13,14,14,14,13,13,13,9,14,14,14,15,14,15,14,15,15,14,15,15,15,14,15,15,15,15,15,14,15,15,15,15,15,15,12,14,15,15,13,15,15,15,15,16,16,16,15,16,14,16,16,14,16,13,16,16,16,15,16,13,16,15,16,14,9,16,16,16,16,16,16,16,16,16,13,14,16,16,15,16,16,10,16,15,16,14,16,16,14,16,16,14,16,16,14,15,16,16,16,14,15,14,15,13,16,16,15,17,17,17,17,17,17,14,15,17,17,16,16,17,16,15,17,16,17,11,17,16,17,16,17,16,17,17,16,17,17,16,17,17,16,16,17,17,17,16,14,17,17,17,17,15,16,14,16,15,16,13,16,15,16,14,16,15,16,12,16,15,16,17,17,17,17,17,13,16,15,17,17,17,16,15,17,17,17,16,15,17,17,14,16,17,17,16,17,17,16,15,17,16,14,17,16,15,17,16,17,17,16,17,15,16,17,14,17,16,15,17,16,17,13,17,16,17,17,16,17,14,17,16,17,16,17,16,17,9]; + /** + * Vertical blur radius in pixels + * @property blurY + * @default 0 + * @type Number + **/ + this.blurY = blurY | 0; + + /** + * Number of blur iterations. For example, a value of 1 will produce a rough blur. A value of 2 will produce a + * smoother blur, but take twice as long to run. + * @property quality + * @default 1 + * @type Number + **/ + this.quality = quality | 0; + } + var p = createjs.extend(BlurFilter, createjs.Filter); + + + +// constants: + /** + * Array of multiply values for blur calculations. + * @property MUL_TABLE + * @type Array + * @protected + * @static + **/ + BlurFilter.MUL_TABLE = [1,171,205,293,57,373,79,137,241,27,391,357,41,19,283,265,497,469,443,421,25,191,365,349,335,161,155,149,9,278,269,261,505,245,475,231,449,437,213,415,405,395,193,377,369,361,353,345,169,331,325,319,313,307,301,37,145,285,281,69,271,267,263,259,509,501,493,243,479,118,465,459,113,446,55,435,429,423,209,413,51,403,199,393,97,3,379,375,371,367,363,359,355,351,347,43,85,337,333,165,327,323,5,317,157,311,77,305,303,75,297,294,73,289,287,71,141,279,277,275,68,135,67,133,33,262,260,129,511,507,503,499,495,491,61,121,481,477,237,235,467,232,115,457,227,451,7,445,221,439,218,433,215,427,425,211,419,417,207,411,409,203,202,401,399,396,197,49,389,387,385,383,95,189,47,187,93,185,23,183,91,181,45,179,89,177,11,175,87,173,345,343,341,339,337,21,167,83,331,329,327,163,81,323,321,319,159,79,315,313,39,155,309,307,153,305,303,151,75,299,149,37,295,147,73,291,145,289,287,143,285,71,141,281,35,279,139,69,275,137,273,17,271,135,269,267,133,265,33,263,131,261,130,259,129,257,1]; + + /** + * Array of shift values for blur calculations. + * @property SHG_TABLE + * @type Array + * @protected + * @static + **/ + BlurFilter.SHG_TABLE = [0,9,10,11,9,12,10,11,12,9,13,13,10,9,13,13,14,14,14,14,10,13,14,14,14,13,13,13,9,14,14,14,15,14,15,14,15,15,14,15,15,15,14,15,15,15,15,15,14,15,15,15,15,15,15,12,14,15,15,13,15,15,15,15,16,16,16,15,16,14,16,16,14,16,13,16,16,16,15,16,13,16,15,16,14,9,16,16,16,16,16,16,16,16,16,13,14,16,16,15,16,16,10,16,15,16,14,16,16,14,16,16,14,16,16,14,15,16,16,16,14,15,14,15,13,16,16,15,17,17,17,17,17,17,14,15,17,17,16,16,17,16,15,17,16,17,11,17,16,17,16,17,16,17,17,16,17,17,16,17,17,16,16,17,17,17,16,14,17,17,17,17,15,16,14,16,15,16,13,16,15,16,14,16,15,16,12,16,15,16,17,17,17,17,17,13,16,15,17,17,17,16,15,17,17,17,16,15,17,17,14,16,17,17,16,17,17,16,15,17,16,14,17,16,15,17,16,17,17,16,17,15,16,17,14,17,16,15,17,16,17,13,17,16,17,17,16,17,14,17,16,17,16,17,16,17,9]; // public methods: /** docced in super class **/ @@ -10629,43 +10331,44 @@ var p = BlurFilter.prototype = new createjs.Filter(); return new createjs.Rectangle(-this.blurX*q,-this.blurY*q,2*this.blurX*q,2*this.blurY*q); }; - p.applyFilter = function(ctx, x, y, width, height, targetCtx, targetX, targetY) { - targetCtx = targetCtx || ctx; - if (targetX == null) { targetX = x; } - if (targetY == null) { targetY = y; } - try { - var imageData = ctx.getImageData(x, y, width, height); - } catch(e) { - //if (!this.suppressCrossDomainErrors) throw new Error("unable to access local image data: " + e); - return false; - } + /** docced in super class **/ + p.clone = function() { + return new BlurFilter(this.blurX, this.blurY, this.quality); + }; + /** docced in super class **/ + p.toString = function() { + return "[BlurFilter]"; + }; + + +// private methods: + + /** docced in super class **/ + p._applyFilter = function(imageData) { var radiusX = this.blurX/2; - if ( isNaN(radiusX) || radiusX < 0 ) return false; + if (isNaN(radiusX) || radiusX < 0 ) { return false; } radiusX |= 0; var radiusY = this.blurY/2; - if ( isNaN(radiusY) || radiusY < 0 ) return false; + if (isNaN(radiusY) || radiusY < 0 ) { return false; } radiusY |= 0; - if ( radiusX == 0 && radiusY == 0 ) return false; + if ( radiusX == 0 && radiusY == 0 ) { return false; } var iterations = this.quality; - if ( isNaN(iterations) || iterations < 1 ) iterations = 1; + if (isNaN(iterations) || iterations < 1) { iterations = 1; } iterations |= 0; - if ( iterations > 3 ) iterations = 3; - if ( iterations < 1 ) iterations = 1; + if ( iterations > 3 ) { iterations = 3; } + if ( iterations < 1 ) { iterations = 1; } + + var pixels = imageData.data, width = imageData.width, height=imageData.height; - var pixels = imageData.data; - - var x, y, i, p, yp, yi, yw, r_sum, g_sum, b_sum, a_sum, - r_out_sum, g_out_sum, b_out_sum, a_out_sum, - r_in_sum, g_in_sum, b_in_sum, a_in_sum, - pr, pg, pb, pa, rbs; + // TODO: there are a lot of unused variables in this method: + var x, y, i, p, yp, yi, yw, r_sum, g_sum, b_sum, a_sum, pr, pg, pb, pa; var divx = radiusX + radiusX + 1; var divy = radiusY + radiusY + 1; - var w4 = width << 2; var widthMinus1 = width - 1; var heightMinus1 = height - 1; var rxp1 = radiusX + 1; @@ -10675,7 +10378,6 @@ var p = BlurFilter.prototype = new createjs.Filter(); for ( i = 1; i < divx; i++ ) { stackx = stackx.next = {r:0,b:0,g:0,a:0,next:null}; - if ( i == rxp1 ) var stackEndX = stackx; } stackx.next = stackStartX; @@ -10684,19 +10386,18 @@ var p = BlurFilter.prototype = new createjs.Filter(); for ( i = 1; i < divy; i++ ) { stacky = stacky.next = {r:0,b:0,g:0,a:0,next:null}; - if ( i == ryp1 ) var stackEndY = stacky; } stacky.next = stackStartY; var stackIn = null; - - + var mul_table = BlurFilter.MUL_TABLE; + var shg_table = BlurFilter.SHG_TABLE; while ( iterations-- > 0 ) { yw = yi = 0; - var mul_sum = this.mul_table[radiusX]; - var shg_sum = this.shg_table[radiusX]; + var mul_sum = mul_table[radiusX]; + var shg_sum = shg_table[radiusX]; for ( y = height; --y > -1; ) { r_sum = rxp1 * ( pr = pixels[yi] ); @@ -10747,8 +10448,8 @@ var p = BlurFilter.prototype = new createjs.Filter(); yw += width; } - mul_sum = this.mul_table[radiusY]; - shg_sum = this.shg_table[radiusY]; + mul_sum = mul_table[radiusY]; + shg_sum = shg_table[radiusY]; for ( x = 0; x < width; x++ ) { yi = x << 2; @@ -10845,63 +10546,23 @@ var p = BlurFilter.prototype = new createjs.Filter(); } } } - targetCtx.putImageData(imageData, targetX, targetY); return true; }; - /** - * Returns a clone of this object. - * @method clone - * @return {BlurFilter} - **/ - p.clone = function() { - return new BlurFilter(this.blurX, this.blurY, this.quality); - }; - - p.toString = function() { - return "[BlurFilter]"; - }; - - createjs.BlurFilter = BlurFilter; - + createjs.BlurFilter = createjs.promote(BlurFilter, "Filter"); }()); -/* - * AlphaMapFilter - * Visit http://createjs.com/ for documentation, updates and examples. - * - * Copyright (c) 2010 gskinner.com, inc. - * - * Permission is hereby granted, free of charge, to any person - * obtaining a copy of this software and associated documentation - * files (the "Software"), to deal in the Software without - * restriction, including without limitation the rights to use, - * copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the - * Software is furnished to do so, subject to the following - * conditions: - * - * The above copyright notice and this permission notice shall be - * included in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES - * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT - * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, - * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR - * OTHER DEALINGS IN THE SOFTWARE. - */ -/** - * @module EaselJS - */ +//############################################################################## +// AlphaMapFilter.js +//############################################################################## -// namespace: this.createjs = this.createjs || {}; (function () { "use strict"; + + +// constructor: /** * Applies a greyscale alpha map image (or canvas) to the target, such that the alpha channel of the result will * be copied from the red channel of the map, and the RGB channels will be copied from the target. @@ -10931,85 +10592,73 @@ this.createjs = this.createjs || {}; * @param {Image|HTMLCanvasElement} alphaMap The greyscale image (or canvas) to use as the alpha value for the * result. This should be exactly the same dimensions as the target. **/ - var AlphaMapFilter = function (alphaMap) { - this.initialize(alphaMap); - }; - var p = AlphaMapFilter.prototype = new createjs.Filter(); - -// constructor: - /** @ignore */ - p.initialize = function (alphaMap) { + function AlphaMapFilter(alphaMap) { + + + // public properties: + /** + * The greyscale image (or canvas) to use as the alpha value for the result. This should be exactly the same + * dimensions as the target. + * @property alphaMap + * @type Image|HTMLCanvasElement + **/ this.alphaMap = alphaMap; - }; + + + // private properties: + /** + * @property _alphaMap + * @protected + * @type Image|HTMLCanvasElement + **/ + this._alphaMap = null; + + /** + * @property _mapData + * @protected + * @type Uint8ClampedArray + **/ + this._mapData = null; + } + var p = createjs.extend(AlphaMapFilter, createjs.Filter); -// public properties: - - /** - * The greyscale image (or canvas) to use as the alpha value for the result. This should be exactly the same - * dimensions as the target. - * @property alphaMap - * @type Image|HTMLCanvasElement - **/ - p.alphaMap = null; - -// private properties: - p._alphaMap = null; - p._mapData = null; // public methods: - - p.applyFilter = function (ctx, x, y, width, height, targetCtx, targetX, targetY) { - if (!this.alphaMap) { - return true; - } - if (!this._prepAlphaMap()) { - return false; - } - targetCtx = targetCtx || ctx; - if (targetX == null) { - targetX = x; - } - if (targetY == null) { - targetY = y; - } - - try { - var imageData = ctx.getImageData(x, y, width, height); - } catch (e) { - //if (!this.suppressCrossDomainErrors) throw new Error("unable to access local image data: " + e); - return false; - } - var data = imageData.data; - var map = this._mapData; - var l = data.length; - for(var i = 0; i < l; i += 4) { - data[i + 3] = map[i] || 0; - } - targetCtx.putImageData(imageData, targetX, targetY); - return true; - }; - - /** - * Returns a clone of this object. - * @method clone - * @return {AlphaMapFilter} A clone of the current AlphaMapFilter instance. - **/ + /** docced in super class **/ p.clone = function () { - return new AlphaMapFilter(this.alphaMap); + var o = new AlphaMapFilter(this.alphaMap); + o._alphaMap = this._alphaMap; + o._mapData = this._mapData; + return o; }; + /** docced in super class **/ p.toString = function () { return "[AlphaMapFilter]"; }; + // private methods: + /** docced in super class **/ + p._applyFilter = function (imageData) { + if (!this.alphaMap) { return true; } + if (!this._prepAlphaMap()) { return false; } + + // TODO: update to support scenarios where the target has different dimensions. + var data = imageData.data; + var map = this._mapData; + for(var i=0, l=data.length; iExample - * This example draws a red circle, and then transforms it to Blue. This is accomplished by multiplying all the channels - * to 0 (except alpha, which is set to 1), and then adding 255 to the blue channel. - * - * var shape = new createjs.Shape().set({x:100,y:100}); - * shape.graphics.beginFill("#ff0000").drawCircle(0,0,50); - * - * shape.filters = [ - * new createjs.ColorFilter(0,0,0,1, 0,0,255,0) - * ]; - * shape.cache(-50, -50, 100, 100); - * - * See {{#crossLink "Filter"}}{{/crossLink}} for an more information on applying filters. - * @class ColorFilter - * @param {Number} [redMultiplier=1] The amount to multiply against the red channel. This is a range between 0 and 1. - * @param {Number} [greenMultiplier=1] The amount to multiply against the green channel. This is a range between 0 and 1. - * @param {Number} [blueMultiplier=1] The amount to multiply against the blue channel. This is a range between 0 and 1. - * @param {Number} [alphaMultiplier=1] The amount to multiply against the alpha channel. This is a range between 0 and 1. - * @param {Number} [redOffset=0] The amount to add to the red channel after it has been multiplied. This is a range - * between -255 and 255. - * @param {Number} [greenOffset=0] The amount to add to the green channel after it has been multiplied. This is a range - * between -255 and 255. - * @param {Number} [blueOffset=0] The amount to add to the blue channel after it has been multiplied. This is a range - * between -255 and 255. - * @param {Number} [alphaOffset=0] The amount to add to the alpha channel after it has been multiplied. This is a range - * between -255 and 255. - * @constructor - * @extends Filter - **/ -var ColorFilter = function(redMultiplier, greenMultiplier, blueMultiplier, alphaMultiplier, redOffset, greenOffset, blueOffset, alphaOffset) { - this.initialize(redMultiplier, greenMultiplier, blueMultiplier, alphaMultiplier, redOffset, greenOffset, blueOffset, alphaOffset); -} -var p = ColorFilter.prototype = new createjs.Filter(); - -// public properties: - /** - * Red channel multiplier. - * @property redMultiplier - * @type Number - **/ - p.redMultiplier = 1; - - /** - * Green channel multiplier. - * @property greenMultiplier - * @type Number - **/ - p.greenMultiplier = 1; - - /** - * Blue channel multiplier. - * @property blueMultiplier - * @type Number - **/ - p.blueMultiplier = 1; - - /** - * Alpha channel multiplier. - * @property alphaMultiplier - * @type Number - **/ - p.alphaMultiplier = 1; - - /** - * Red channel offset (added to value). - * @property redOffset - * @type Number - **/ - p.redOffset = 0; - - /** - * Green channel offset (added to value). - * @property greenOffset - * @type Number - **/ - p.greenOffset = 0; - - /** - * Blue channel offset (added to value). - * @property blueOffset - * @type Number - **/ - p.blueOffset = 0; - - /** - * Alpha channel offset (added to value). - * @property alphaOffset - * @type Number - **/ - p.alphaOffset = 0; // constructor: /** - * Initialization method. - * @method initialize + * Applies a color transform to DisplayObjects. + * + *

Example

+ * This example draws a red circle, and then transforms it to Blue. This is accomplished by multiplying all the channels + * to 0 (except alpha, which is set to 1), and then adding 255 to the blue channel. + * + * var shape = new createjs.Shape().set({x:100,y:100}); + * shape.graphics.beginFill("#ff0000").drawCircle(0,0,50); + * + * shape.filters = [ + * new createjs.ColorFilter(0,0,0,1, 0,0,255,0) + * ]; + * shape.cache(-50, -50, 100, 100); + * + * See {{#crossLink "Filter"}}{{/crossLink}} for an more information on applying filters. + * @class ColorFilter * @param {Number} [redMultiplier=1] The amount to multiply against the red channel. This is a range between 0 and 1. * @param {Number} [greenMultiplier=1] The amount to multiply against the green channel. This is a range between 0 and 1. * @param {Number} [blueMultiplier=1] The amount to multiply against the blue channel. This is a range between 0 and 1. @@ -11327,35 +10827,92 @@ var p = ColorFilter.prototype = new createjs.Filter(); * @param {Number} [redOffset=0] The amount to add to the red channel after it has been multiplied. This is a range * between -255 and 255. * @param {Number} [greenOffset=0] The amount to add to the green channel after it has been multiplied. This is a range - * between -255 and 255. + * between -255 and 255. * @param {Number} [blueOffset=0] The amount to add to the blue channel after it has been multiplied. This is a range - * between -255 and 255. + * between -255 and 255. * @param {Number} [alphaOffset=0] The amount to add to the alpha channel after it has been multiplied. This is a range - * between -255 and 255. - * @protected + * between -255 and 255. + * @constructor + * @extends Filter **/ - p.initialize = function(redMultiplier, greenMultiplier, blueMultiplier, alphaMultiplier, redOffset, greenOffset, blueOffset, alphaOffset) { + function ColorFilter(redMultiplier, greenMultiplier, blueMultiplier, alphaMultiplier, redOffset, greenOffset, blueOffset, alphaOffset) { + + + // public properties: + /** + * Red channel multiplier. + * @property redMultiplier + * @type Number + **/ this.redMultiplier = redMultiplier != null ? redMultiplier : 1; + + /** + * Green channel multiplier. + * @property greenMultiplier + * @type Number + **/ this.greenMultiplier = greenMultiplier != null ? greenMultiplier : 1; + + /** + * Blue channel multiplier. + * @property blueMultiplier + * @type Number + **/ this.blueMultiplier = blueMultiplier != null ? blueMultiplier : 1; + + /** + * Alpha channel multiplier. + * @property alphaMultiplier + * @type Number + **/ this.alphaMultiplier = alphaMultiplier != null ? alphaMultiplier : 1; + + /** + * Red channel offset (added to value). + * @property redOffset + * @type Number + **/ this.redOffset = redOffset || 0; + + /** + * Green channel offset (added to value). + * @property greenOffset + * @type Number + **/ this.greenOffset = greenOffset || 0; + + /** + * Blue channel offset (added to value). + * @property blueOffset + * @type Number + **/ this.blueOffset = blueOffset || 0; + + /** + * Alpha channel offset (added to value). + * @property alphaOffset + * @type Number + **/ this.alphaOffset = alphaOffset || 0; } + var p = createjs.extend(ColorFilter, createjs.Filter); + // public methods: - p.applyFilter = function(ctx, x, y, width, height, targetCtx, targetX, targetY) { - targetCtx = targetCtx || ctx; - if (targetX == null) { targetX = x; } - if (targetY == null) { targetY = y; } - try { - var imageData = ctx.getImageData(x, y, width, height); - } catch(e) { - //if (!this.suppressCrossDomainErrors) throw new Error("unable to access local image data: " + e); - return false; - } + /** docced in super class **/ + p.toString = function() { + return "[ColorFilter]"; + }; + + /** docced in super class **/ + p.clone = function() { + return new ColorFilter(this.redMultiplier, this.greenMultiplier, this.blueMultiplier, this.alphaMultiplier, this.redOffset, this.greenOffset, this.blueOffset, this.alphaOffset); + }; + + +// private methods: + /** docced in super class **/ + p._applyFilter = function(imageData) { var data = imageData.data; var l = data.length; for (var i=0; iExample + * * myColorMatrix.adjustHue(20).adjustBrightness(50); * * See {{#crossLink "Filter"}}{{/crossLink}} for an example of how to apply filters, or {{#crossLink "ColorMatrixFilter"}}{{/crossLink}} @@ -11439,11 +10956,13 @@ this.createjs = this.createjs||{}; * @param {Number} hue * @constructor **/ - var ColorMatrix = function(brightness, contrast, saturation, hue) { - this.initialize(brightness, contrast, saturation, hue); - }; + function ColorMatrix(brightness, contrast, saturation, hue) { + this.setColor(brightness, contrast, saturation, hue); + } var p = ColorMatrix.prototype; + +// constants: /** * Array of delta values for contrast calculations. * @property DELTA_INDEX @@ -11490,19 +11009,18 @@ this.createjs = this.createjs||{}; ColorMatrix.LENGTH = ColorMatrix.IDENTITY_MATRIX.length; +// public methods: /** - * Initialization method. - * @method initialize + * Resets the instance with the specified values. + * @method setColor * @param {Number} brightness * @param {Number} contrast * @param {Number} saturation * @param {Number} hue * @protected */ - p.initialize = function(brightness,contrast,saturation,hue) { - this.reset(); - this.adjustColor(brightness,contrast,saturation,hue); - return this; + p.setColor = function(brightness,contrast,saturation,hue) { + return this.reset().adjustColor(brightness,contrast,saturation,hue);; }; /** @@ -11691,23 +11209,23 @@ this.createjs = this.createjs||{}; return "[ColorMatrix]"; }; -// private methods: +// private methods: /** * @method _multiplyMatrix * @param {Array} matrix * @protected **/ p._multiplyMatrix = function(matrix) { - var col = []; + var i, j, k, col = []; - for (var i=0;i<5;i++) { - for (var j=0;j<5;j++) { + for (i=0;i<5;i++) { + for (j=0;j<5;j++) { col[j] = this[j+i*5]; } - for (var j=0;j<5;j++) { + for (j=0;j<5;j++) { var val=0; - for (var k=0;k<5;k++) { + for (k=0;k<5;k++) { val += matrix[j+k*5]*col[k]; } this[j+i*5] = val; @@ -11726,7 +11244,6 @@ this.createjs = this.createjs||{}; return Math.min(limit,Math.max(-limit,value)); }; - // /** * Makes sure matrixes are 5x5 (25 long). * @method _fixMatrix @@ -11743,102 +11260,74 @@ this.createjs = this.createjs||{}; return matrix; }; + createjs.ColorMatrix = ColorMatrix; - }()); -/* -* ColorMatrixFilter -* Visit http://createjs.com/ for documentation, updates and examples. -* -* Copyright (c) 2010 gskinner.com, inc. -* -* Permission is hereby granted, free of charge, to any person -* obtaining a copy of this software and associated documentation -* files (the "Software"), to deal in the Software without -* restriction, including without limitation the rights to use, -* copy, modify, merge, publish, distribute, sublicense, and/or sell -* copies of the Software, and to permit persons to whom the -* Software is furnished to do so, subject to the following -* conditions: -* -* The above copyright notice and this permission notice shall be -* included in all copies or substantial portions of the Software. -* -* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES -* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT -* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR -* OTHER DEALINGS IN THE SOFTWARE. -*/ -/** - * @module EaselJS - */ +//############################################################################## +// ColorMatrixFilter.js +//############################################################################## -// namespace: this.createjs = this.createjs||{}; (function() { "use strict"; -/** - * Allows you to carry out complex color operations such as modifying saturation, brightness, or inverting. See the - * {{#crossLink "ColorMatrix"}}{{/crossLink}} for more information on changing colors. For an easier color transform, - * consider the {{#crossLink "ColorFilter"}}{{/crossLink}}. - * - *

Example

- * This example creates a red circle, inverts its hue, and then saturates it to brighten it up. - * - * var shape = new createjs.Shape().set({x:100,y:100}); - * shape.graphics.beginFill("#ff0000").drawCircle(0,0,50); - * - * var matrix = new createjs.ColorMatrix().adjustHue(180).adjustSaturation(100); - * shape.filters = [ - * new createjs.ColorMatrixFilter(matrix) - * ]; - * - * shape.cache(-50, -50, 100, 100); - * - * See {{#crossLink "Filter"}}{{/crossLink}} for an more information on applying filters. - * @class ColorMatrixFilter - * @constructor - * @extends Filter - * @param {Array} matrix A 4x5 matrix describing the color operation to perform. See also the {{#crossLink "ColorMatrix"}}{{/crossLink}} - * class. - **/ -var ColorMatrixFilter = function(matrix) { - this.initialize(matrix); -}; -var p = ColorMatrixFilter.prototype = new createjs.Filter(); - -// public properties: - p.matrix = null; // constructor: - // TODO: detailed docs. /** - * @method initialize - * @protected - * @param {Array} matrix A 4x5 matrix describing the color operation to perform. + * Allows you to carry out complex color operations such as modifying saturation, brightness, or inverting. See the + * {{#crossLink "ColorMatrix"}}{{/crossLink}} for more information on changing colors. For an easier color transform, + * consider the {{#crossLink "ColorFilter"}}{{/crossLink}}. + * + *

Example

+ * This example creates a red circle, inverts its hue, and then saturates it to brighten it up. + * + * var shape = new createjs.Shape().set({x:100,y:100}); + * shape.graphics.beginFill("#ff0000").drawCircle(0,0,50); + * + * var matrix = new createjs.ColorMatrix().adjustHue(180).adjustSaturation(100); + * shape.filters = [ + * new createjs.ColorMatrixFilter(matrix) + * ]; + * + * shape.cache(-50, -50, 100, 100); + * + * See {{#crossLink "Filter"}}{{/crossLink}} for an more information on applying filters. + * @class ColorMatrixFilter + * @constructor + * @extends Filter + * @param {Array | ColorMatrix} matrix A 4x5 matrix describing the color operation to perform. See also the {{#crossLink "ColorMatrix"}}{{/crossLink}} + * class. **/ - p.initialize = function(matrix) { + function ColorMatrixFilter(matrix) { + + + // public properties: + /** + * A 4x5 matrix describing the color operation to perform. See also the {{#crossLink "ColorMatrix"}}{{/crossLink}} + * @property matrix + * @type Array | ColorMatrix + **/ this.matrix = matrix; - }; + } + var p = createjs.extend(ColorMatrixFilter, createjs.Filter); + // public methods: - p.applyFilter = function(ctx, x, y, width, height, targetCtx, targetX, targetY) { - targetCtx = targetCtx || ctx; - if (targetX == null) { targetX = x; } - if (targetY == null) { targetY = y; } - try { - var imageData = ctx.getImageData(x, y, width, height); - } catch(e) { - //if (!this.suppressCrossDomainErrors) throw new Error("unable to access local image data: " + e); - return false; - } + /** docced in super class **/ + p.toString = function() { + return "[ColorMatrixFilter]"; + }; + + /** docced in super class **/ + p.clone = function() { + return new ColorMatrixFilter(this.matrix); + }; + +// private methods: + /** docced in super class **/ + p._applyFilter = function(imageData) { var data = imageData.data; var l = data.length; var r,g,b,a; @@ -11858,66 +11347,25 @@ var p = ColorMatrixFilter.prototype = new createjs.Filter(); data[i+2] = r*m10+g*m11+b*m12+a*m13+m14; // blue data[i+3] = r*m15+g*m16+b*m17+a*m18+m19; // alpha } - targetCtx.putImageData(imageData, targetX, targetY); return true; }; - p.toString = function() { - return "[ColorMatrixFilter]"; - }; - - /** - * Returns a clone of this ColorMatrixFilter instance. - * @method clone - * @return {ColorMatrixFilter} A clone of the current ColorMatrixFilter instance. - **/ - p.clone = function() { - return new ColorMatrixFilter(this.matrix); - }; - - createjs.ColorMatrixFilter = ColorMatrixFilter; + createjs.ColorMatrixFilter = createjs.promote(ColorMatrixFilter, "Filter"); }()); -/* -* Touch -* Visit http://createjs.com/ for documentation, updates and examples. -* -* Copyright (c) 2010 gskinner.com, inc. -* -* Permission is hereby granted, free of charge, to any person -* obtaining a copy of this software and associated documentation -* files (the "Software"), to deal in the Software without -* restriction, including without limitation the rights to use, -* copy, modify, merge, publish, distribute, sublicense, and/or sell -* copies of the Software, and to permit persons to whom the -* Software is furnished to do so, subject to the following -* conditions: -* -* The above copyright notice and this permission notice shall be -* included in all copies or substantial portions of the Software. -* -* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES -* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT -* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR -* OTHER DEALINGS IN THE SOFTWARE. -*/ -/** - * @module EaselJS - */ +//############################################################################## +// Touch.js +//############################################################################## -// namespace: this.createjs = this.createjs||{}; (function() { "use strict"; -// TODO: support for double tap. -/** + +// constructor: + /** * Global utility for working with multi-touch enabled devices in EaselJS. Currently supports W3C Touch API (iOS and * modern Android browser) and the Pointer API (IE), including ms-prefixed events in IE10, and unprefixed in IE11. * @@ -11936,11 +11384,12 @@ this.createjs = this.createjs||{}; * @class Touch * @static **/ -var Touch = function() { - throw "Touch cannot be instantiated"; -}; + function Touch() { + throw "Touch cannot be instantiated"; + } -// Public static methods: + +// public static methods: /** * Returns `true` if touch is supported in the current browser. * @method isSupported @@ -11948,9 +11397,9 @@ var Touch = function() { * @static **/ Touch.isSupported = function() { - return ('ontouchstart' in window) // iOS + return !!(('ontouchstart' in window) // iOS & Android || (window.navigator['msPointerEnabled'] && window.navigator['msMaxTouchPoints'] > 0) // IE10 - || (window.navigator['pointerEnabled'] && window.navigator['maxTouchPoints'] > 0); // IE11+ + || (window.navigator['pointerEnabled'] && window.navigator['maxTouchPoints'] > 0)); // IE11+ }; /** @@ -11969,6 +11418,7 @@ var Touch = function() { **/ Touch.enable = function(stage, singleTouch, allowDefault) { if (!stage || !stage.canvas || !Touch.isSupported()) { return false; } + if (stage.__touch) { return true; } // inject required properties on stage: stage.__touch = {pointers:{}, multitouch:!singleTouch, preventDefault:!allowDefault, count:0}; @@ -11990,10 +11440,12 @@ var Touch = function() { if (!stage) { return; } if ('ontouchstart' in window) { Touch._IOS_disable(stage); } else if (window.navigator['msPointerEnabled'] || window.navigator["pointerEnabled"]) { Touch._IE_disable(stage); } + + delete stage.__touch; }; -// Private static methods: +// Private static methods: /** * @method _IOS_enable * @protected @@ -12134,7 +11586,6 @@ var Touch = function() { } }; - /** * @method _handleStart * @param {Stage} stage @@ -12186,11 +11637,13 @@ var Touch = function() { }; -createjs.Touch = Touch; + createjs.Touch = Touch; }()); -/** - * @module EaselJS - */ + +//############################################################################## +// version.js +//############################################################################## + this.createjs = this.createjs || {}; (function() { @@ -12209,7 +11662,7 @@ this.createjs = this.createjs || {}; * @type String * @static **/ - s.version = /*version*/"NEXT"; // injected by build process + s.version = /*=version*/""; // injected by build process /** * The build date for this release in UTC format. @@ -12217,6 +11670,6 @@ this.createjs = this.createjs || {}; * @type String * @static **/ - s.buildDate = /*date*/"Tue, 28 Jan 2014 21:54:26 GMT"; // injected by build process + s.buildDate = /*=date*/""; // injected by build process -})(); +})(); \ No newline at end of file diff --git a/vendor/scripts/preloadjs-NEXT.combined.js b/vendor/scripts/preloadjs-NEXT.combined.js index 039c8aa61..c20d9a86c 100644 --- a/vendor/scripts/preloadjs-NEXT.combined.js +++ b/vendor/scripts/preloadjs-NEXT.combined.js @@ -27,7 +27,7 @@ this.createjs = this.createjs||{}; * @type String * @static **/ - s.buildDate = /*date*/"Thu, 06 Mar 2014 22:58:10 GMT"; // injected by build process + s.buildDate = /*date*/"Wed, 22 Oct 2014 16:11:35 GMT"; // injected by build process })(); /* @@ -78,7 +78,7 @@ this.createjs = this.createjs||{}; /** * Contains properties and methods shared by all events for use with * {{#crossLink "EventDispatcher"}}{{/crossLink}}. - * + * * Note that Event objects are often reused, so you should never * rely on an event object's state outside of the call stack it was received in. * @class Event @@ -91,6 +91,7 @@ var Event = function(type, bubbles, cancelable) { this.initialize(type, bubbles, cancelable); }; var p = Event.prototype; +Event.prototype.constructor = Event; // events: @@ -194,7 +195,7 @@ var p = Event.prototype; * @readonly */ p.immediatePropagationStopped = false; - + /** * Indicates if {{#crossLink "Event/remove"}}{{/crossLink}} has been called on this event. * @property removed @@ -249,21 +250,21 @@ var p = Event.prototype; p.stopImmediatePropagation = function() { this.immediatePropagationStopped = this.propagationStopped = true; }; - + /** * Causes the active listener to be removed via removeEventListener(); - * + * * myBtn.addEventListener("click", function(evt) { * // do stuff... * evt.remove(); // removes this listener. * }); - * + * * @method remove **/ p.remove = function() { this.removed = true; }; - + /** * Returns a clone of the Event instance. * @method clone @@ -375,6 +376,7 @@ var EventDispatcher = function() { /* this.initialize(); */ // not needed. }; var p = EventDispatcher.prototype; +EventDispatcher.prototype.constructor = EventDispatcher; /** @@ -579,19 +581,19 @@ var p = EventDispatcher.prototype; * @param {Object | String | Event} eventObj An object with a "type" property, or a string type. * While a generic object will work, it is recommended to use a CreateJS Event instance. If a string is used, * dispatchEvent will construct an Event instance with the specified type. - * @param {Object} [target] The object to use as the target property of the event object. This will default to the - * dispatching object. This parameter is deprecated and will be removed. * @return {Boolean} Returns the value of eventObj.defaultPrevented. **/ - p.dispatchEvent = function(eventObj, target) { + p.dispatchEvent = function(eventObj) { if (typeof eventObj == "string") { // won't bubble, so skip everything if there's no listeners: var listeners = this._listeners; if (!listeners || !listeners[eventObj]) { return false; } eventObj = new createjs.Event(eventObj); + } else if (eventObj.target && eventObj.clone) { + // redispatching an active event object, so clone it: + eventObj = eventObj.clone(); } - // TODO: deprecated. Target param is deprecated, only use case is MouseEvent/mousemove, remove. - eventObj.target = target||this; + try { eventObj.target = this; } catch (e) {} // try/catch allows redispatching of native events if (!eventObj.bubbles || !this.parent) { this._dispatchEvent(eventObj, 2); @@ -663,8 +665,8 @@ var p = EventDispatcher.prototype; if (eventObj && listeners) { var arr = listeners[eventObj.type]; if (!arr||!(l=arr.length)) { return; } - eventObj.currentTarget = this; - eventObj.eventPhase = eventPhase; + try { eventObj.currentTarget = this; } catch (e) {} + try { eventObj.eventPhase = eventPhase; } catch (e) {} eventObj.removed = false; arr = arr.slice(); // to avoid issues with items being removed or added during the dispatch for (var i=0; i + *
  • If the path is absolute. Absolute paths start with a protocol (such as `http://`, `file://`, or + * `//networkPath`)
  • + *
  • If the path is relative. Relative paths start with `../` or `/path` (or similar)
  • + *
  • The file extension. This is determined by the filename with an extension. Query strings are dropped, and + * the file path is expected to follow the format `name.ext`.
  • + * + * + * Note: This has changed from earlier versions, which used a single, complicated Regular Expression, which + * was difficult to maintain, and over-aggressive in determining all file properties. It has been simplified to + * only pull out what it needs. + * @param path + * @returns {Object} An Object with an `absolute` and `relative` Boolean, as well as an optional 'extension` String + * property, which is the lowercase extension. + * @private */ p._parseURI = function(path) { - if (!path) { return null; } - return path.match(s.FILE_PATTERN); - }; + var info = { absolute: false, relative:false }; + if (path == null) { return info; }; - /** - * Parse a file URI using the {{#crossLink "AbstractLoader/PATH_PATTERN"}}{{/crossLink}} RegExp pattern. - * @method _parsePath - * @param {String} path The file path to parse. - * @return {Array} The matched path contents. Please see the PATH_PATTERN property for details on the return value. - * This will return null if it does not match. - * @protected - */ - p._parsePath = function(path) { - if (!path) { return null; } - return path.match(s.PATH_PATTERN); + // Drop the query string + var queryIndex = path.indexOf("?"); + if (queryIndex > -1) { + path = path.substr(0,queryIndex); + } + + // Absolute + var match; + if (s.ABSOLUTE_PATT.test(path)) { + info.absolute = true; + + // Relative + } else if (s.RELATIVE_PATT.test(path)) { + info.relative = true; + } + + // Extension + if (match = path.match(s.EXTENSION_PATT)) { + info.extension = match[1].toLowerCase(); + } + return info; }; /** @@ -1541,6 +1571,7 @@ TODO: WINDOWS ISSUES }; var p = LoadQueue.prototype = new createjs.AbstractLoader(); + LoadQueue.prototype.constructor = LoadQueue; var s = LoadQueue; /** @@ -2078,7 +2109,7 @@ TODO: WINDOWS ISSUES for (var n in this._loadItemsById) { this._disposeItem(this._loadItemsById[n]); } - this.init(this.useXHR); + this.init(this.useXHR, this._basePath, this._crossOrigin); // Remove specific items } else { @@ -2593,7 +2624,7 @@ TODO: WINDOWS ISSUES // Determine Extension, etc. var match = this._parseURI(item.src); - if (match != null) { item.ext = match[6]; } + if (match.extension) { item.ext = match.extension; } if (item.type == null) { item.type = this._getTypeByExtension(item.ext); } @@ -2602,13 +2633,13 @@ TODO: WINDOWS ISSUES var bp = ""; // Store the generated basePath var useBasePath = basePath || this._basePath; var autoId = item.src; - if (match && match[1] == null && match[3] == null) { + if (!match.absolute && !match.relative) { if (path) { bp = path; - var pathMatch = this._parsePath(path); + var pathMatch = this._parseURI(path); autoId = path + autoId; // Also append basePath - if (useBasePath != null && pathMatch && pathMatch[1] == null && pathMatch[2] == null) { + if (useBasePath != null && !pathMatch.absolute && !pathMatch.relative) { bp = useBasePath + bp; } } else if (useBasePath != null) { @@ -2666,8 +2697,8 @@ TODO: WINDOWS ISSUES // Update the extension in case the type changed: match = this._parseURI(item.src); - if (match != null && match[6] != null) { - item.ext = match[6].toLowerCase(); + if (match.extension != null) { + item.ext = match.extension; } } } @@ -3252,6 +3283,7 @@ this.createjs = this.createjs||{}; }; var p = TagLoader.prototype = new createjs.AbstractLoader(); + TagLoader.prototype.constructor = TagLoader; // Protected @@ -3396,7 +3428,16 @@ this.createjs = this.createjs||{}; item.type == createjs.LoadQueue.CSS) { this._startTagVisibility = tag.style.visibility; tag.style.visibility = "hidden"; - (document.body || document.getElementsByTagName("body")[0]).appendChild(tag); + var node = document.body || document.getElementsByTagName("body")[0]; + if (node == null) { + if (item.type == createjs.LoadQueue.SVG) { + this._handleSVGError(); + return; + } else { + node = document.head || document.getElementsByTagName("head"); + } + } + node.appendChild(tag); } // Note: Previous versions didn't seem to work when we called load() for OGG tags in Firefox. Seems fixed in 15.0.1 @@ -3405,6 +3446,13 @@ this.createjs = this.createjs||{}; } }; + p._handleSVGError = function() { + this._clean(); + var event = new createjs.Event("error"); + event.text = "SVG_NO_BODY"; + this._sendError(event); + }; + p._handleJSONPLoad = function(data) { this._jsonResult = data; }; @@ -3488,8 +3536,8 @@ this.createjs = this.createjs||{}; // case createjs.LoadQueue.CSS: //LM: We may need to remove CSS tags loaded using a LINK tag.style.visibility = this._startTagVisibility; - (document.body || document.getElementsByTagName("body")[0]).removeChild(tag); - break; + tag.parentNode && tag.parentNode.contains(tag) && tag.parentNode.removeChild(tag); + break; default: } @@ -3618,6 +3666,7 @@ this.createjs = this.createjs || {}; ]; var p = XHRLoader.prototype = new createjs.AbstractLoader(); + XHRLoader.prototype.constructor = XHRLoader; //Protected /** diff --git a/vendor/scripts/soundjs-NEXT.combined.js b/vendor/scripts/soundjs-NEXT.combined.js index 09e8920c7..346983e77 100644 --- a/vendor/scripts/soundjs-NEXT.combined.js +++ b/vendor/scripts/soundjs-NEXT.combined.js @@ -27,7 +27,7 @@ this.createjs = this.createjs || {}; * @type String * @static **/ - s.buildDate = /*date*/"Tue, 21 Jan 2014 18:00:36 GMT"; // injected by build process + s.buildDate = /*date*/"Mon, 27 Oct 2014 20:40:07 GMT"; // injected by build process })(); /* @@ -121,6 +121,7 @@ var EventDispatcher = function() { /* this.initialize(); */ // not needed. }; var p = EventDispatcher.prototype; +EventDispatcher.prototype.constructor = EventDispatcher; /** @@ -325,19 +326,19 @@ var p = EventDispatcher.prototype; * @param {Object | String | Event} eventObj An object with a "type" property, or a string type. * While a generic object will work, it is recommended to use a CreateJS Event instance. If a string is used, * dispatchEvent will construct an Event instance with the specified type. - * @param {Object} [target] The object to use as the target property of the event object. This will default to the - * dispatching object. This parameter is deprecated and will be removed. * @return {Boolean} Returns the value of eventObj.defaultPrevented. **/ - p.dispatchEvent = function(eventObj, target) { + p.dispatchEvent = function(eventObj) { if (typeof eventObj == "string") { // won't bubble, so skip everything if there's no listeners: var listeners = this._listeners; if (!listeners || !listeners[eventObj]) { return false; } eventObj = new createjs.Event(eventObj); + } else if (eventObj.target && eventObj.clone) { + // redispatching an active event object, so clone it: + eventObj = eventObj.clone(); } - // TODO: deprecated. Target param is deprecated, only use case is MouseEvent/mousemove, remove. - eventObj.target = target||this; + try { eventObj.target = this; } catch (e) {} // try/catch allows redispatching of native events if (!eventObj.bubbles || !this.parent) { this._dispatchEvent(eventObj, 2); @@ -409,8 +410,8 @@ var p = EventDispatcher.prototype; if (eventObj && listeners) { var arr = listeners[eventObj.type]; if (!arr||!(l=arr.length)) { return; } - eventObj.currentTarget = this; - eventObj.eventPhase = eventPhase; + try { eventObj.currentTarget = this; } catch (e) {} + try { eventObj.eventPhase = eventPhase; } catch (e) {} eventObj.removed = false; arr = arr.slice(); // to avoid issues with items being removed or added during the dispatch for (var i=0; iExample + * * myObject.addEventListener("change", createjs.proxy(myMethod, scope)); * * @module CreateJS @@ -489,6 +491,7 @@ var Event = function(type, bubbles, cancelable) { this.initialize(type, bubbles, cancelable); }; var p = Event.prototype; +Event.prototype.constructor = Event; // events: @@ -670,6 +673,18 @@ var p = Event.prototype; p.clone = function() { return new Event(this.type, this.bubbles, this.cancelable); }; + + /** + * Provides a chainable shortcut method for setting a number of properties on the instance. + * + * @method set + * @param {Object} props A generic object containing properties to copy to the instance. + * @return {Event} Returns the instance the method is called on (useful for chaining calls.) + */ + p.set = function(props) { + for (var n in props) { this[n] = props[n]; } + return this; + }; /** * Returns a string representation of this object. @@ -856,6 +871,72 @@ this.createjs = this.createjs||{}; }; } +}());/* +* defineProperty +* Visit http://createjs.com/ for documentation, updates and examples. +* +* Copyright (c) 2010 gskinner.com, inc. +* +* Permission is hereby granted, free of charge, to any person +* obtaining a copy of this software and associated documentation +* files (the "Software"), to deal in the Software without +* restriction, including without limitation the rights to use, +* copy, modify, merge, publish, distribute, sublicense, and/or sell +* copies of the Software, and to permit persons to whom the +* Software is furnished to do so, subject to the following +* conditions: +* +* The above copyright notice and this permission notice shall be +* included in all copies or substantial portions of the Software. +* +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +* OTHER DEALINGS IN THE SOFTWARE. +*/ + +/** + * @module CreateJS + */ + +// namespace: +this.createjs = this.createjs||{}; + +/** + * @class Utility Methods + */ +(function() { + "use strict"; + + /** + * Boolean value indicating if Object.defineProperty is supported. + * + * @property definePropertySupported + * @type {Boolean} + * @default true + */ + var t = Object.defineProperty ? true : false; + + // IE8 has Object.defineProperty, but only for DOM objects, so check if fails to suppress errors + var foo = {}; + try { + Object.defineProperty(foo, "bar", { + get: function() { + return this._bar; + }, + set: function(value) { + this._bar = value; + } + }); + } catch (e) { + t = false; + }; + + createjs.definePropertySupported = t; }());/* * Sound * Visit http://createjs.com/ for documentation, updates and examples. @@ -920,8 +1001,8 @@ this.createjs = this.createjs || {}; * } * *

    Browser Support

    - * Audio will work in browsers which support HTMLAudioElement (http://caniuse.com/audio) - * or WebAudio (http://caniuse.com/audio-api). A Flash fallback can be added + * Audio will work in browsers which support WebAudio (http://caniuse.com/audio-api) + * or HTMLAudioElement (http://caniuse.com/audio). A Flash fallback can be added * as well, which will work in any browser that supports the Flash player. * @module SoundJS * @main SoundJS @@ -931,11 +1012,6 @@ this.createjs = this.createjs || {}; "use strict"; - //TODO: Interface to validate plugins and throw warnings - //TODO: Determine if methods exist on a plugin before calling // OJR this is only an issue if something breaks or user changes something - //TODO: Interface to validate instances and throw warnings - //TODO: Surface errors on audio from all plugins - //TODO: Timeouts // OJR for? /** * The Sound class is the public API for creating sounds, controlling the overall sound levels, and managing plugins. * All Sound APIs on this class are static. @@ -945,7 +1021,7 @@ this.createjs = this.createjs || {}; * or register multiple sounds using {{#crossLink "Sound/registerManifest"}}{{/crossLink}}. If you don't register a * sound prior to attempting to play it using {{#crossLink "Sound/play"}}{{/crossLink}} or create it using {{#crossLink "Sound/createInstance"}}{{/crossLink}}, * the sound source will be automatically registered but playback will fail as the source will not be ready. If you use - * PreloadJS, this is handled for you when the sound is + * PreloadJS, registration is handled for you when the sound is * preloaded. It is recommended to preload sounds either internally using the register functions or externally using * PreloadJS so they are ready when you want to use them. * @@ -982,11 +1058,55 @@ this.createjs = this.createjs || {}; * * Sound can be used as a plugin with PreloadJS to help preload audio properly. Audio preloaded with PreloadJS is * automatically registered with the Sound class. When audio is not preloaded, Sound will do an automatic internal - * load. As a result, it may not play immediately the first time play is called. Use the + * load. As a result, it may fail to play the first time play is called if the audio is not finished loading. Use the * {{#crossLink "Sound/fileload"}}{{/crossLink}} event to determine when a sound has finished internally preloading. * It is recommended that all audio is preloaded before it is played. * - * createjs.PreloadJS.installPlugin(createjs.Sound); + * var queue = new createjs.LoadQueue(); + * queue.installPlugin(createjs.Sound); + * + * Audio Sprites
    + * SoundJS has added support for Audio Sprites, available as of version 0.5.3. + * For those unfamiliar with audio sprites, they are much like CSS sprites or sprite sheets: multiple audio assets + * grouped into a single file. + * + * Benefits of Audio Sprites + *
    • More robust support for older browsers and devices that only allow a single audio instance, such as iOS 5.
    • + *
    • They provide a work around for the Internet Explorer 9 audio tag limit, which until now restricted how many + * different sounds we could load at once.
    • + *
    • Faster loading by only requiring a single network request for several sounds, especially on mobile devices + * where the network round trip for each file can add significant latency.
    + * + * Drawbacks of Audio Sprites + *
    • No guarantee of smooth looping when using HTML or Flash audio. If you have a track that needs to loop + * smoothly and you are supporting non-web audio browsers, do not use audio sprites for that sound if you can avoid it.
    • + *
    • No guarantee that HTML audio will play back immediately, especially the first time. In some browsers (Chrome!), + * HTML audio will only load enough to play through – so we rely on the “canplaythrough” event to determine if the audio is loaded. + * Since audio sprites must jump ahead to play specific sounds, the audio may not yet have downloaded.
    • + *
    • Audio sprites share the same core source, so if you have a sprite with 5 sounds and are limited to 2 + * concurrently playing instances, that means you can only play 2 of the sounds at the same time.
    + * + *

    Example

    + * createjs.Sound.initializeDefaultPlugins(); + * var assetsPath = "./assets/"; + * var manifest = [{ + * src:"MyAudioSprite.ogg", data: { + * audioSprite: [ + * {id:"sound1", startTime:0, duration:500}, + * {id:"sound2", startTime:1000, duration:400}, + * {id:"sound3", startTime:1700, duration: 1000} + * ]} + * } + * ]; + * createjs.Sound.alternateExtensions = ["mp3"]; + * createjs.Sound.addEventListener("fileload", loadSound); + * createjs.Sound.registerManifest(manifest, assetsPath); + * // after load is complete + * createjs.Sound.play("sound2"); + * + * You can also create audio sprites on the fly by setting the startTime and duration when creating an new SoundInstance. + * + * createjs.Sound.play("MyAudioSprite", {startTime: 1000, duration: 400}); * * Mobile Safe Approach
    * Mobile devices require sounds to be played inside of a user initiated event (touch/click) in varying degrees. @@ -1009,6 +1129,7 @@ this.createjs = this.createjs || {}; * when or how you apply the volume change, as the tag seems to need to play to apply it. *
  • MP3 encoding will not always work for audio tags, particularly in Internet Explorer. We've found default * encoding with 64kbps works.
  • + *
  • Occasionally very short samples will get cut off.
  • *
  • There is a limit to how many audio tags you can load and play at once, which appears to be determined by * hardware and browser settings. See {{#crossLink "HTMLAudioPlugin.MAX_INSTANCES"}}{{/crossLink}} for a safe estimate.
  • * @@ -1044,19 +1165,16 @@ this.createjs = this.createjs || {}; var s = Sound; + // TODO DEPRECATED /** - * DEPRECATED - * This approach has is being replaced by {{#crossLink "Sound/alternateExtensions:property"}}{{/crossLink}}, and - * support will be removed in the next version. - * - * The character (or characters) that are used to split multiple paths from an audio source. + * REMOVED + * Use {{#crossLink "Sound/alternateExtensions:property"}}{{/crossLink}} instead * @property DELIMITER * @type {String} * @default | * @static * @deprecated */ - s.DELIMITER = "|"; /** * The interrupt value to interrupt any currently playing instance with the same source, if the maximum number of @@ -1159,7 +1277,7 @@ this.createjs = this.createjs || {}; * @default ["mp3", "ogg", "mpeg", "wav", "m4a", "mp4", "aiff", "wma", "mid"] * @since 0.4.0 */ - s.SUPPORTED_EXTENSIONS = ["mp3", "ogg", "mpeg", "wav", "m4a", "mp4", "aiff", "wma", "mid"]; // OJR FlashPlugin does not currently support + s.SUPPORTED_EXTENSIONS = ["mp3", "ogg", "mpeg", "wav", "m4a", "mp4", "aiff", "wma", "mid"]; /** * Some extensions use another type of extension support to play (one of them is a codex). This allows you to map @@ -1291,7 +1409,7 @@ this.createjs = this.createjs || {}; s._instances = []; /** - * An object hash storing sound sources via there corresponding ID. + * An object hash storing objects with sound sources, startTime, and duration via there corresponding ID. * @property _idHash * @type {Object} * @protected @@ -1346,16 +1464,6 @@ this.createjs = this.createjs || {}; * @since 0.4.1 */ - //TODO: Deprecated - /** - * REMOVED. Use {{#crossLink "EventDispatcher/addEventListener"}}{{/crossLink}} and the {{#crossLink "Sound/fileload:event"}}{{/crossLink}} - * event. - * @property onLoadComplete - * @type {Function} - * @deprecated Use addEventListener and the fileload event. - * @since 0.4.0 - */ - /** * Used by external plugins to dispatch file load events. * @method _sendFileLoadEvent @@ -1365,9 +1473,8 @@ this.createjs = this.createjs || {}; * @since 0.4.1 */ s._sendFileLoadEvent = function (src) { - if (!s._preloadHash[src]) { - return; - } + if (!s._preloadHash[src]) {return;} + for (var i = 0, l = s._preloadHash[src].length; i < l; i++) { var item = s._preloadHash[src][i]; s._preloadHash[src][i] = true; @@ -1378,6 +1485,7 @@ this.createjs = this.createjs || {}; event.src = item.src; event.id = item.id; event.data = item.data; + event.sprite = item.sprite; s.dispatchEvent(event); } @@ -1406,33 +1514,7 @@ this.createjs = this.createjs || {}; }; /** - * Deprecated in favor of {{#crossLink "Sound/registerPlugins"}}{{/crossLink}} with a single argument. - * createjs.Sound.registerPlugins([createjs.WebAudioPlugin]); - * - * @method registerPlugin - * @param {Object} plugin The plugin class to install. - * @return {Boolean} Whether the plugin was successfully initialized. - * @static - * @deprecated - */ - s.registerPlugin = function (plugin) { - try { - console.log("createjs.Sound.registerPlugin has been deprecated. Please use registerPlugins."); - } catch (err) { - // you are in IE with the console closed, you monster - } - return s._registerPlugin(plugin); - }; - - /** - * Register a Sound plugin. Plugins handle the actual playback of audio. The default plugins are - * ({{#crossLink "WebAudioPlugin"}}{{/crossLink}} followed by {{#crossLink "HTMLAudioPlugin"}}{{/crossLink}}), - * and are installed if no other plugins are present when the user attempts to start playback or register sound. - *

    Example

    - * createjs.FlashPlugin.swfPath = "../src/SoundJS/"; - * createjs.Sound._registerPlugin(createjs.FlashPlugin); - * - * To register multiple plugins, use {{#crossLink "Sound/registerPlugins"}}{{/crossLink}}. + * Used by {{#crossLink "Sound/registerPlugins"}}{{/crossLink}} to register a Sound plugin. * * @method _registerPlugin * @param {Object} plugin The plugin class to install. @@ -1441,14 +1523,9 @@ this.createjs = this.createjs || {}; * @private */ s._registerPlugin = function (plugin) { - s._pluginsRegistered = true; - if (plugin == null) { - return false; - } // Note: Each plugin is passed in as a class reference, but we store the activePlugin as an instance if (plugin.isSupported()) { s.activePlugin = new plugin(); - //TODO: Check error on initialization return true; } return false; @@ -1467,9 +1544,9 @@ this.createjs = this.createjs || {}; * @static */ s.registerPlugins = function (plugins) { + s._pluginsRegistered = true; for (var i = 0, l = plugins.length; i < l; i++) { - var plugin = plugins[i]; - if (s._registerPlugin(plugin)) { + if (s._registerPlugin(plugins[i])) { return true; } } @@ -1489,15 +1566,9 @@ this.createjs = this.createjs || {}; * @since 0.4.0 */ s.initializeDefaultPlugins = function () { - if (s.activePlugin != null) { - return true; - } - if (s._pluginsRegistered) { - return false; - } - if (s.registerPlugins([createjs.WebAudioPlugin, createjs.HTMLAudioPlugin])) { - return true; - } + if (s.activePlugin != null) {return true;} + if (s._pluginsRegistered) {return false;} + if (s.registerPlugins([createjs.WebAudioPlugin, createjs.HTMLAudioPlugin])) {return true;} return false; }; @@ -1544,9 +1615,7 @@ this.createjs = this.createjs || {}; * @static */ s.getCapabilities = function () { - if (s.activePlugin == null) { - return null; - } + if (s.activePlugin == null) {return null;} return s.activePlugin._capabilities; }; @@ -1564,9 +1633,7 @@ this.createjs = this.createjs || {}; * @see getCapabilities */ s.getCapability = function (key) { - if (s.activePlugin == null) { - return null; - } + if (s.activePlugin == null) {return null;} return s.activePlugin._capabilities[key]; }; @@ -1580,20 +1647,79 @@ this.createjs = this.createjs || {}; * @param {String} [id] An optional user-specified id that is used to play sounds. * @param {Number|String|Boolean|Object} [data] Data associated with the item. Sound uses the data parameter as the * number of channels for an audio instance, however a "channels" property can be appended to the data object if - * this property is used for other information. The audio channels will default to 1 if no value is found. - * @param {String} [path] A combined basepath and subPath from PreloadJS that has already been prepended to src. + * this property is used for other information. The audio channels will set a default based on plugin if no value is found. * @return {Boolean|Object} An object with the modified values of those that were passed in, or false if the active * plugin can not play the audio type. * @protected * @static */ - s.initLoad = function (src, type, id, data, path) { - // remove path from src so we can continue to support "|" splitting of src files // TODO remove this when "|" is removed - src = src.replace(path, ""); - var details = s.registerSound(src, id, data, false, path); - if (details == null) { - return false; + s.initLoad = function (src, type, id, data) { + return s._registerSound(src, id, data); + }; + + /** + * Internal method for loading sounds. This should not be called directly. + * + * @method _registerSound + * @param {String | Object} src The source to load. + * @param {String} [id] An id specified by the user to play the sound later. + * @param {Number | Object} [data] Data associated with the item. Sound uses the data parameter as the number of + * channels for an audio instance, however a "channels" property can be appended to the data object if it is used + * for other information. The audio channels will set a default based on plugin if no value is found. + * Sound also uses the data property to hold an audioSprite array of objects in the following format {id, startTime, duration}.
    + * id used to play the sound later, in the same manner as a sound src with an id.
    + * startTime is the initial offset to start playback and loop from, in milliseconds.
    + * duration is the amount of time to play the clip for, in milliseconds.
    + * This allows Sound to support audio sprites that are played back by id. + * @return {Object} An object with the modified values that were passed in, which defines the sound. + * Returns false if the source cannot be parsed or no plugins can be initialized. + * Returns true if the source is already loaded. + * @static + * @private + * @since 0.5.3 + */ + + s._registerSound = function (src, id, data) { + if (!s.initializeDefaultPlugins()) {return false;} + + var details = s._parsePath(src); + if (details == null) {return false;} + details.type = "sound"; + details.id = id; + details.data = data; + + var numChannels = s.activePlugin.defaultNumChannels || null; + if (data != null) { + if (!isNaN(data.channels)) { + numChannels = parseInt(data.channels); + } else if (!isNaN(data)) { + numChannels = parseInt(data); + } + + if(data.audioSprite) { + var sp; + for(var i = data.audioSprite.length; i--; ) { + sp = data.audioSprite[i]; + s._idHash[sp.id] = {src: details.src, startTime: parseInt(sp.startTime), duration: parseInt(sp.duration)}; + } + } } + if (id != null) {s._idHash[id] = {src: details.src}}; + var loader = s.activePlugin.register(details.src, numChannels); // Note only HTML audio uses numChannels + + SoundChannel.create(details.src, numChannels); + + // return the number of instances to the user. This will also be returned in the load event. + if (data == null || !isNaN(data)) { + details.data = numChannels || SoundChannel.maxPerChannel(); + } else { + details.data.channels = numChannels || SoundChannel.maxPerChannel(); + } + + details.tag = loader.tag; + if (loader.completeHandler) {details.completeHandler = loader.completeHandler;} + if (loader.type) {details.type = loader.type;} + return details; }; @@ -1613,8 +1739,11 @@ this.createjs = this.createjs || {}; * @param {Number | Object} [data] Data associated with the item. Sound uses the data parameter as the number of * channels for an audio instance, however a "channels" property can be appended to the data object if it is used * for other information. The audio channels will set a default based on plugin if no value is found. - * @param {Boolean} [preload=true] If the sound should be internally preloaded so that it can be played back - * without an external preloader. This is currently used by PreloadJS when loading sounds to disable internal preloading. + * Sound also uses the data property to hold an audioSprite array of objects in the following format {id, startTime, duration}.
    + * id used to play the sound later, in the same manner as a sound src with an id.
    + * startTime is the initial offset to start playback and loop from, in milliseconds.
    + * duration is the amount of time to play the clip for, in milliseconds.
    + * This allows Sound to support audio sprites that are played back by id. * @param {string} basePath Set a path that will be prepended to src for loading. * @return {Object} An object with the modified values that were passed in, which defines the sound. * Returns false if the source cannot be parsed or no plugins can be initialized. @@ -1622,90 +1751,26 @@ this.createjs = this.createjs || {}; * @static * @since 0.4.0 */ - s.registerSound = function (src, id, data, preload, basePath) { - if (!s.initializeDefaultPlugins()) { - return false; - } - + s.registerSound = function (src, id, data, basePath) { if (src instanceof Object) { - basePath = id; //this assumes preload has not be passed in as a property // OJR check if arguments == 3 would be less fragile - //?? preload = src.preload; - // OJR refactor how data is passed in to make the parameters work better + basePath = id; id = src.id; data = src.data; src = src.src; } - // branch to different parse based on alternate formats setting - if (s.alternateExtensions.length) { - var details = s._parsePath2(src, "sound", id, data); + if (basePath != null) {src = basePath + src;} + + var details = s._registerSound(src, id, data); + if(!details) {return false;} + + if (!s._preloadHash[details.src]) { s._preloadHash[details.src] = [];} + s._preloadHash[details.src].push({src:src, id:id, data:details.data}); + if (s._preloadHash[details.src].length == 1) { + // OJR note this will disallow reloading a sound if loading fails or the source changes + s.activePlugin.preload(details.src, details.tag); } else { - var details = s._parsePath(src, "sound", id, data); - } - if (details == null) { - return false; - } - if (basePath != null) { - src = basePath + src; - details.src = basePath + details.src; - } - - if (id != null) { - s._idHash[id] = details.src; - } - - var numChannels = null; // null tells SoundChannel to set this to it's internal maxDefault - if (data != null) { - if (!isNaN(data.channels)) { - numChannels = parseInt(data.channels); - } - else if (!isNaN(data)) { - numChannels = parseInt(data); - } - } - var loader = s.activePlugin.register(details.src, numChannels); // Note only HTML audio uses numChannels - - if (loader != null) { // all plugins currently return a loader - if (loader.numChannels != null) { - numChannels = loader.numChannels; - } // currently only HTMLAudio returns this - SoundChannel.create(details.src, numChannels); - - // return the number of instances to the user. This will also be returned in the load event. - if (data == null || !isNaN(data)) { - data = details.data = numChannels || SoundChannel.maxPerChannel(); - } else { - data.channels = details.data.channels = numChannels || SoundChannel.maxPerChannel(); - } - - // If the loader returns a tag, return it instead for preloading. - // OJR all loaders currently use tags? - if (loader.tag != null) { - details.tag = loader.tag; - } else if (loader.src) { - details.src = loader.src; - } - // If the loader returns a complete handler, pass it on to the prelaoder. - if (loader.completeHandler != null) { - details.completeHandler = loader.completeHandler; - } - if (loader.type) { - details.type = loader.type; - } - } - - if (preload != false) { - if (!s._preloadHash[details.src]) { - s._preloadHash[details.src] = []; - } // we do this so we can store multiple id's and data if needed - s._preloadHash[details.src].push({src:src, id:id, data:data}); // keep this data so we can return it in fileload event - if (s._preloadHash[details.src].length == 1) { - // if already loaded once, don't load a second time // OJR note this will disallow reloading a sound if loading fails or the source changes - s.activePlugin.preload(details.src, loader); - } else { - // if src already loaded successfully, return true - if (s._preloadHash[details.src][0] == true) {return true;} - } + if (s._preloadHash[details.src][0] == true) {return true;} } return details; @@ -1741,8 +1806,8 @@ this.createjs = this.createjs || {}; s.registerManifest = function (manifest, basePath) { var returnValues = []; for (var i = 0, l = manifest.length; i < l; i++) { - returnValues[i] = createjs.Sound.registerSound(manifest[i].src, manifest[i].id, manifest[i].data, manifest[i].preload, basePath); - } // OJR consider removing .preload from args, as it is only used by PreloadJS + returnValues[i] = createjs.Sound.registerSound(manifest[i].src, manifest[i].id, manifest[i].data, basePath); + } return returnValues; }; @@ -1764,29 +1829,18 @@ this.createjs = this.createjs || {}; * @since 0.4.1 */ s.removeSound = function(src, basePath) { - if (s.activePlugin == null) { - return false; - } + if (s.activePlugin == null) {return false;} - if (src instanceof Object) { - src = src.src; - } - src = s._getSrcById(src); + if (src instanceof Object) {src = src.src;} + src = s._getSrcById(src).src; + if (basePath != null) {src = basePath + src;} - if (s.alternateExtensions.length) { - var details = s._parsePath2(src); - } else { - var details = s._parsePath(src); - } - if (details == null) { - return false; - } - if (basePath != null) {details.src = basePath + details.src;} + var details = s._parsePath(src); + if (details == null) {return false;} src = details.src; - // remove src from _idHash // Note "for in" can be a slow operation for(var prop in s._idHash){ - if(s._idHash[prop] == src) { + if(s._idHash[prop].src == src) { delete(s._idHash[prop]); } } @@ -1794,10 +1848,8 @@ this.createjs = this.createjs || {}; // clear from SoundChannel, which also stops and deletes all instances SoundChannel.removeSrc(src); - // remove src from _preloadHash delete(s._preloadHash[src]); - // activePlugin cleanup s.activePlugin.removeSound(src); return true; @@ -1850,7 +1902,7 @@ this.createjs = this.createjs || {}; s._idHash = {}; s._preloadHash = {}; SoundChannel.removeAll(); - s.activePlugin.removeAllSounds(); + if (s.activePlugin) {s.activePlugin.removeAllSounds();} }; /** @@ -1869,89 +1921,42 @@ this.createjs = this.createjs || {}; * @since 0.4.0 */ s.loadComplete = function (src) { - if (s.alternateExtensions.length) { - var details = s._parsePath2(src, "sound"); - } else { - var details = s._parsePath(src, "sound"); - } + if (!s.isReady()) { return false; } + var details = s._parsePath(src); if (details) { - src = s._getSrcById(details.src); + src = s._getSrcById(details.src).src; } else { - src = s._getSrcById(src); + src = s._getSrcById(src).src; } return (s._preloadHash[src][0] == true); // src only loads once, so if it's true for the first it's true for all }; /** - * Parse the path of a sound, usually from a manifest item. Manifest items support single file paths, as well as - * composite paths using {{#crossLink "Sound/DELIMITER:property"}}{{/crossLink}}, which defaults to "|". The first path supported by the - * current browser/plugin will be used. - * NOTE the "|" approach is deprecated and will be removed in the next version + * Parse the path of a sound, usually from a manifest item. alternate extensions will be attempted in order if the + * current extension is not supported * @method _parsePath * @param {String} value The path to an audio source. - * @param {String} [type] The type of path. This will typically be "sound" or null. - * @param {String} [id] The user-specified sound ID. This may be null, in which case the src will be used instead. - * @param {Number | String | Boolean | Object} [data] Arbitrary data appended to the sound, usually denoting the - * number of channels for the sound. This method doesn't currently do anything with the data property. * @return {Object} A formatted object that can be registered with the {{#crossLink "Sound/activePlugin:property"}}{{/crossLink}} * and returned to a preloader like PreloadJS. * @protected */ - s._parsePath = function (value, type, id, data) { - if (typeof(value) != "string") {value = value.toString();} - var sounds = value.split(s.DELIMITER); - if (sounds.length > 1) { - try { - console.log("createjs.Sound.DELIMITER \"|\" loading approach has been deprecated. Please use the new alternateExtensions property."); - } catch (err) { - // you are in IE with the console closed, you monster - } - } - var ret = {type:type || "sound", id:id, data:data}; - var c = s.getCapabilities(); - for (var i = 0, l = sounds.length; i < l; i++) { - var sound = sounds[i]; - - var match = sound.match(s.FILE_PATTERN); - if (match == null) { - return false; - } - var name = match[4]; - var ext = match[5]; - - if (c[ext] && createjs.indexOf(s.SUPPORTED_EXTENSIONS, ext) > -1) { - ret.name = name; - ret.src = sound; - ret.extension = ext; - return ret; - } - } - return null; - }; - - // new approach, when old approach is deprecated this will become _parsePath - s._parsePath2 = function (value, type, id, data) { + s._parsePath = function (value) { if (typeof(value) != "string") {value = value.toString();} var match = value.match(s.FILE_PATTERN); - if (match == null) { - return false; - } + if (match == null) {return false;} + var name = match[4]; var ext = match[5]; - var c = s.getCapabilities(); var i = 0; while (!c[ext]) { ext = s.alternateExtensions[i++]; if (i > s.alternateExtensions.length) { return null;} // no extensions are supported } - value = value.replace("."+match[5], "."+ext); - var ret = {type:type || "sound", id:id, data:data}; - ret.name = name; - ret.src = value; - ret.extension = ext; + + var ret = {name:name, src:value, extension:ext}; return ret; }; @@ -1976,6 +1981,9 @@ this.createjs = this.createjs || {}; * var myInstance = createjs.Sound.play("myAudioPath/mySound.mp3", createjs.Sound.INTERRUPT_ANY, 0, 0, -1, 1, 0); * } * + * NOTE to create an audio sprite that has not already been registered, both startTime and duration need to be set. + * This is only when creating a new audio sprite, not when playing using the id of an already registered audio sprite. + * * @method play * @param {String} src The src or ID of the audio. * @param {String | Object} [interrupt="none"|options] How to interrupt any currently playing instances of audio with the same source, @@ -1983,7 +1991,7 @@ this.createjs = this.createjs || {}; * constants on the Sound class, with the default defined by {{#crossLink "Sound/defaultInterruptBehavior:property"}}{{/crossLink}}. *
    OR
    * This parameter can be an object that contains any or all optional properties by name, including: interrupt, - * delay, offset, loop, volume, and pan (see the above code sample). + * delay, offset, loop, volume, pan, startTime, and duration (see the above code sample). * @param {Number} [delay=0] The amount of time to delay the start of audio playback, in milliseconds. * @param {Number} [offset=0] The offset from the start of the audio to begin playback, in milliseconds. * @param {Number} [loop=0] How many times the audio loops when it reaches the end of playback. The default is 0 (no @@ -1991,16 +1999,26 @@ this.createjs = this.createjs || {}; * @param {Number} [volume=1] The volume of the sound, between 0 and 1. Note that the master volume is applied * against the individual volume. * @param {Number} [pan=0] The left-right pan of the sound (if supported), between -1 (left) and 1 (right). + * @param {Number} [startTime=null] To create an audio sprite (with duration), the initial offset to start playback and loop from, in milliseconds. + * @param {Number} [duration=null] To create an audio sprite (with startTime), the amount of time to play the clip for, in milliseconds. * @return {SoundInstance} A {{#crossLink "SoundInstance"}}{{/crossLink}} that can be controlled after it is created. * @static */ - s.play = function (src, interrupt, delay, offset, loop, volume, pan) { - var instance = s.createInstance(src); + s.play = function (src, interrupt, delay, offset, loop, volume, pan, startTime, duration) { + if (interrupt instanceof Object) { + delay = interrupt.delay; + offset = interrupt.offset; + loop = interrupt.loop; + volume = interrupt.volume; + pan = interrupt.pan; + startTime = interrupt.startTime; + duration = interrupt.duration; + interrupt = interrupt.interrupt; - var ok = s._playInstance(instance, interrupt, delay, offset, loop, volume, pan); - if (!ok) { - instance.playFailed(); } + var instance = s.createInstance(src, startTime, duration); + var ok = s._playInstance(instance, interrupt, delay, offset, loop, volume, pan); + if (!ok) {instance.playFailed();} return instance; }; @@ -2019,34 +2037,30 @@ this.createjs = this.createjs || {}; * myInstance = createjs.Sound.createInstance("myAudioPath/mySound.mp3"); * } * + * NOTE to create an audio sprite that has not already been registered, both startTime and duration need to be set. + * This is only when creating a new audio sprite, not when playing using the id of an already registered audio sprite. + * * @method createInstance * @param {String} src The src or ID of the audio. + * @param {Number} [startTime=null] To create an audio sprite (with duration), the initial offset to start playback and loop from, in milliseconds. + * @param {Number} [duration=null] To create an audio sprite (with startTime), the amount of time to play the clip for, in milliseconds. * @return {SoundInstance} A {{#crossLink "SoundInstance"}}{{/crossLink}} that can be controlled after it is created. * Unsupported extensions will return the default SoundInstance. * @since 0.4.0 */ - s.createInstance = function (src) { - if (!s.initializeDefaultPlugins()) { - return s._defaultSoundInstance; - } + s.createInstance = function (src, startTime, duration) { + if (!s.initializeDefaultPlugins()) {return s._defaultSoundInstance;} src = s._getSrcById(src); - if (s.alternateExtensions.length) { - var details = s._parsePath2(src, "sound"); - } else { - var details = s._parsePath(src, "sound"); - } + var details = s._parsePath(src.src); var instance = null; if (details != null && details.src != null) { - // make sure that we have a sound channel (sound is registered or previously played) SoundChannel.create(details.src); - instance = s.activePlugin.create(details.src); + if (startTime == null) {startTime = src.startTime;} + instance = s.activePlugin.create(details.src, startTime, duration || src.duration); } else { - // the src is not supported, so give back a dummy instance. - // This can happen if PreloadJS fails because the plugin does not support the ext, and was passed an id which - // will not get added to the _idHash. instance = Sound._defaultSoundInstance; } @@ -2068,13 +2082,11 @@ this.createjs = this.createjs || {}; * @static */ s.setVolume = function (value) { - if (Number(value) == null) { - return false; - } + if (Number(value) == null) {return false;} value = Math.max(0, Math.min(1, value)); s._masterVolume = value; if (!this.activePlugin || !this.activePlugin.setVolume || !this.activePlugin.setVolume(value)) { - var instances = this._instances; // OJR does this impact garbage collection more than it helps performance? + var instances = this._instances; for (var i = 0, l = instances.length; i < l; i++) { instances[i].setMasterVolume(value); } @@ -2096,14 +2108,6 @@ this.createjs = this.createjs || {}; return s._masterVolume; }; - /** - * REMOVED. Please see {{#crossLink "Sound/setMute"}}{{/crossLink}}. - * @method mute - * @param {Boolean} value Whether the audio should be muted or not. - * @static - * @deprecated This function has been deprecated. Please use setMute instead. - */ - /** * Mute/Unmute all audio. Note that muted audio still plays at 0 volume. This global mute value is maintained * separately and when set will override, but not change the mute property of individual instances. To mute an individual @@ -2119,9 +2123,7 @@ this.createjs = this.createjs || {}; * @since 0.4.0 */ s.setMute = function (value) { - if (value == null || value == undefined) { - return false; - } + if (value == null) {return false;} this._masterMute = value; if (!this.activePlugin || !this.activePlugin.setMute || !this.activePlugin.setMute(value)) { @@ -2205,15 +2207,13 @@ this.createjs = this.createjs || {}; interrupt = interrupt || s.defaultInterruptBehavior; if (delay == null) {delay = 0;} if (offset == null) {offset = instance.getPosition();} - if (loop == null) {loop = 0;} + if (loop == null) {loop = instance.loop;} if (volume == null) {volume = instance.volume;} if (pan == null) {pan = instance.pan;} if (delay == 0) { var ok = s._beginPlaying(instance, interrupt, offset, loop, volume, pan); - if (!ok) { - return false; - } + if (!ok) {return false;} } else { //Note that we can't pass arguments to proxy OR setTimeout (IE only), so just wrap the function call. // OJR WebAudio may want to handle this differently, so it might make sense to move this functionality into the plugins in the future @@ -2251,11 +2251,8 @@ this.createjs = this.createjs || {}; } var result = instance._beginPlaying(offset, loop, volume, pan); if (!result) { - //LM: Should we remove this from the SoundChannel (see finishedPlaying) var index = createjs.indexOf(this._instances, instance); - if (index > -1) { - this._instances.splice(index, 1); - } + if (index > -1) {this._instances.splice(index, 1);} return false; } return true; @@ -2266,15 +2263,12 @@ this.createjs = this.createjs || {}; * instead. * @method _getSrcById * @param {String} value The ID the sound was registered with. - * @return {String} The source of the sound. Returns null if src has been registered with this id. + * @return {String} The source of the sound if it has been registered with this ID or the value that was passed in. * @protected * @static */ s._getSrcById = function (value) { - if (s._idHash == null || s._idHash[value] == null) { - return value; - } - return s._idHash[value]; + return s._idHash[value] || {src: value}; }; /** @@ -2289,9 +2283,7 @@ this.createjs = this.createjs || {}; s._playFinished = function (instance) { SoundChannel.remove(instance); var index = createjs.indexOf(this._instances, instance); - if (index > -1) { - this._instances.splice(index, 1); - } + if (index > -1) {this._instances.splice(index, 1);} // OJR this will always be > -1, there is no way for an instance to exist without being added to this._instances }; createjs.Sound = Sound; @@ -2352,10 +2344,8 @@ this.createjs = this.createjs || {}; */ SoundChannel.removeSrc = function (src) { var channel = SoundChannel.get(src); - if (channel == null) { - return false; - } - channel.removeAll(); // this stops and removes all active instances + if (channel == null) {return false;} + channel._removeAll(); // this stops and removes all active instances delete(SoundChannel.channels[src]); return true; }; @@ -2366,7 +2356,7 @@ this.createjs = this.createjs || {}; */ SoundChannel.removeAll = function () { for(var channel in SoundChannel.channels) { - SoundChannel.channels[channel].removeAll(); // this stops and removes all active instances + SoundChannel.channels[channel]._removeAll(); // this stops and removes all active instances } SoundChannel.channels = {}; }; @@ -2381,10 +2371,8 @@ this.createjs = this.createjs || {}; */ SoundChannel.add = function (instance, interrupt) { var channel = SoundChannel.get(instance.src); - if (channel == null) { - return false; - } - return channel.add(instance, interrupt); + if (channel == null) {return false;} + return channel._add(instance, interrupt); }; /** * Remove an instance from the channel. @@ -2395,10 +2383,8 @@ this.createjs = this.createjs || {}; */ SoundChannel.remove = function (instance) { var channel = SoundChannel.get(instance.src); - if (channel == null) { - return false; - } - channel.remove(instance); + if (channel == null) {return false;} + channel._remove(instance); return true; }; /** @@ -2420,6 +2406,7 @@ this.createjs = this.createjs || {}; }; var p = SoundChannel.prototype; + p.constructor = SoundChannel; /** * The source of the channel. @@ -2461,9 +2448,7 @@ this.createjs = this.createjs || {}; p.init = function (src, max) { this.src = src; this.max = max || this.maxDefault; - if (this.max == -1) { - this.max = this.maxDefault; - } + if (this.max == -1) {this.max = this.maxDefault;} this._instances = []; }; @@ -2473,7 +2458,7 @@ this.createjs = this.createjs || {}; * @param {Number} index The index to return. * @return {SoundInstance} The SoundInstance at a specific instance. */ - p.get = function (index) { + p._get = function (index) { return this._instances[index]; }; @@ -2483,10 +2468,8 @@ this.createjs = this.createjs || {}; * @param {SoundInstance} instance The instance to add. * @return {Boolean} The success of the method call. If the channel is full, it will return false. */ - p.add = function (instance, interrupt) { - if (!this.getSlot(interrupt, instance)) { - return false; - } + p._add = function (instance, interrupt) { + if (!this._getSlot(interrupt, instance)) {return false;} this._instances.push(instance); this.length++; return true; @@ -2499,11 +2482,9 @@ this.createjs = this.createjs || {}; * @return {Boolean} The success of the remove call. If the instance is not found in this channel, it will * return false. */ - p.remove = function (instance) { + p._remove = function (instance) { var index = createjs.indexOf(this._instances, instance); - if (index == -1) { - return false; - } + if (index == -1) {return false;} this._instances.splice(index, 1); this.length--; return true; @@ -2513,8 +2494,8 @@ this.createjs = this.createjs || {}; * Stop playback and remove all instances from the channel. Usually in response to a delete call. * #method removeAll */ - p.removeAll = function () { - // Note that stop() removes the item from the list, but we don't want to assume that. + p._removeAll = function () { + // Note that stop() removes the item from the list for (var i=this.length-1; i>=0; i--) { this._instances[i].stop(); } @@ -2528,42 +2509,47 @@ this.createjs = this.createjs || {}; * @return {Boolean} Determines if there is an available slot. Depending on the interrupt mode, if there are no slots, * an existing SoundInstance may be interrupted. If there are no slots, this method returns false. */ - p.getSlot = function (interrupt, instance) { + p._getSlot = function (interrupt, instance) { var target, replacement; + if (interrupt != Sound.INTERRUPT_NONE) { + // First replacement candidate + replacement = this._get(0); + if (replacement == null) { + return true; + } + } + for (var i = 0, l = this.max; i < l; i++) { - target = this.get(i); + target = this._get(i); // Available Space if (target == null) { return true; - } else if (interrupt == Sound.INTERRUPT_NONE && target.playState != Sound.PLAY_FINISHED) { - continue; - } - - // First replacement candidate - if (i == 0) { - replacement = target; - continue; } // Audio is complete or not playing if (target.playState == Sound.PLAY_FINISHED || - target.playState == Sound.PLAY_INTERRUPTED || - target.playState == Sound.PLAY_FAILED) { + target.playState == Sound.PLAY_INTERRUPTED || + target.playState == Sound.PLAY_FAILED) { replacement = target; + break; + } - // Audio is a better candidate than the current target, according to playhead - } else if ( - (interrupt == Sound.INTERRUPT_EARLY && target.getPosition() < replacement.getPosition()) || - (interrupt == Sound.INTERRUPT_LATE && target.getPosition() > replacement.getPosition())) { - replacement = target; + if (interrupt == Sound.INTERRUPT_NONE) { + continue; + } + + // Audio is a better candidate than the current target, according to playhead + if ((interrupt == Sound.INTERRUPT_EARLY && target.getPosition() < replacement.getPosition()) || + (interrupt == Sound.INTERRUPT_LATE && target.getPosition() > replacement.getPosition())) { + replacement = target; } } if (replacement != null) { replacement._interrupt(); - this.remove(replacement); + this._remove(replacement); return true; } return false; @@ -2579,7 +2565,7 @@ this.createjs = this.createjs || {}; // This is a dummy sound instance, which allows Sound to return something so developers don't need to check nulls. function SoundInstance() { this.isDefault = true; - this.addEventListener = this.removeEventListener = this.removeAllEventListeners = this.dispatchEvent = this.hasEventListener = this._listeners = this._interrupt = this._playFailed = this.pause = this.resume = this.play = this._beginPlaying = this._cleanUp = this.stop = this.setMasterVolume = this.setVolume = this.mute = this.setMute = this.getMute = this.setPan = this.getPosition = this.setPosition = this.playFailed = function () { + this.addEventListener = this.on = this.off = this.removeEventListener = this.removeAllEventListeners = this.dispatchEvent = this.hasEventListener = this._listeners = this._interrupt = this._playFailed = this.pause = this.resume = this.play = this._beginPlaying = this._cleanUp = this.stop = this.setMasterVolume = this.setVolume = this.mute = this.setMute = this.getMute = this.setPan = this.getPosition = this.setPosition = this.playFailed = function () { return false; }; this.getVolume = this.getPan = this.getDuration = function () { @@ -2613,10 +2599,11 @@ this.createjs = this.createjs || {}; BrowserDetect.init = function () { var agent = window.navigator.userAgent; + BrowserDetect.isWindowPhone = (agent.indexOf("IEMobile") > -1) || (agent.indexOf("Windows Phone") > -1); BrowserDetect.isFirefox = (agent.indexOf("Firefox") > -1); BrowserDetect.isOpera = (window.opera != null); BrowserDetect.isChrome = (agent.indexOf("Chrome") > -1); // NOTE that Chrome on Android returns true but is a completely different browser with different abilities - BrowserDetect.isIOS = agent.indexOf("iPod") > -1 || agent.indexOf("iPhone") > -1 || agent.indexOf("iPad") > -1; + BrowserDetect.isIOS = (agent.indexOf("iPod") > -1 || agent.indexOf("iPhone") > -1 || agent.indexOf("iPad") > -1) && !BrowserDetect.isWindowPhone; BrowserDetect.isAndroid = (agent.indexOf("Android") > -1); BrowserDetect.isBlackberry = (agent.indexOf("Blackberry") > -1); }; @@ -2720,9 +2707,7 @@ this.createjs = this.createjs || {}; // OJR isMobile may be redundant with _isFileXHRSupported available. Consider removing. if (location.protocol == "file:" && !isMobilePhoneGap && !this._isFileXHRSupported()) { return false; } // Web Audio requires XHR, which is not usually available locally s._generateCapabilities(); - if (s.context == null) { - return false; - } + if (s.context == null) {return false;} return true; }; @@ -2740,7 +2725,7 @@ this.createjs = this.createjs || {}; var xhr = new XMLHttpRequest(); try { - xhr.open("GET", "fail.fail", false); // loading non-existant file triggers 404 only if it could load (synchronous call) + xhr.open("GET", "WebAudioPluginTest.fail", false); // loading non-existant file triggers 404 only if it could load (synchronous call) } catch (error) { // catch errors in cases where the onerror is passed by supported = false; @@ -2767,28 +2752,19 @@ this.createjs = this.createjs || {}; * @protected */ s._generateCapabilities = function () { - if (s._capabilities != null) { - return; - } - // Web Audio can be in any formats supported by the audio element, from http://www.w3.org/TR/webaudio/#AudioContext-section, - // therefore tag is still required for the capabilities check + if (s._capabilities != null) {return;} + // Web Audio can be in any formats supported by the audio element, from http://www.w3.org/TR/webaudio/#AudioContext-section var t = document.createElement("audio"); + if (t.canPlayType == null) {return null;} - if (t.canPlayType == null) { - return null; - } - - // This check is first because it's what is currently used, but the spec calls for it to be AudioContext so this - // will probably change in time - if (window.webkitAudioContext) { - s.context = new webkitAudioContext(); - } else if (window.AudioContext) { + if (window.AudioContext) { s.context = new AudioContext(); + } else if (window.webkitAudioContext) { + s.context = new webkitAudioContext(); } else { return null; } - // this handles if only deprecated Web Audio API calls are supported s._compatibilitySetUp(); // playing this inside of a touch event will enable audio on iOS, which starts muted @@ -2814,12 +2790,6 @@ this.createjs = this.createjs || {}; if (s.context.destination.numberOfChannels < 2) { s._capabilities.panning = false; } - - // set up AudioNodes that all of our source audio will connect to - s.dynamicsCompressorNode = s.context.createDynamicsCompressor(); - s.dynamicsCompressorNode.connect(s.context.destination); - s.gainNode = s.context.createGain(); - s.gainNode.connect(s.dynamicsCompressorNode); }; /** @@ -2829,10 +2799,12 @@ this.createjs = this.createjs || {}; * don't support new calls. * * @method _compatibilitySetUp + * @static * @protected * @since 0.4.2 */ s._compatibilitySetUp = function() { + s._panningModel = "equalpower"; //assume that if one new call is supported, they all are if (s.context.createGain) { return; } @@ -2845,7 +2817,7 @@ this.createjs = this.createjs || {}; audioNode.__proto__.stop = audioNode.__proto__.noteOff; // panningModel - this._panningModel = 0; + s._panningModel = 0; }; /** @@ -2855,29 +2827,24 @@ this.createjs = this.createjs || {}; * for example). * *

    Example

    - * * function handleTouch(event) { * createjs.WebAudioPlugin.playEmptySound(); * } * * @method playEmptySound + * @static * @since 0.4.1 */ s.playEmptySound = function() { - // create empty buffer - var buffer = this.context.createBuffer(1, 1, 22050); - var source = this.context.createBufferSource(); - source.buffer = buffer; - - // connect to output (your speakers) - source.connect(this.context.destination); - - // play the file + var source = s.context.createBufferSource(); + source.buffer = s.context.createBuffer(1, 1, 22050); + source.connect(s.context.destination); source.start(0, 0, 0); }; var p = WebAudioPlugin.prototype; + p.constructor = WebAudioPlugin; p._capabilities = null; // doc'd above @@ -2888,7 +2855,6 @@ this.createjs = this.createjs || {}; * @default 1 * @protected */ - // TODO refactor Sound.js so we can use getter setter for volume p._volume = 1; /** @@ -2910,20 +2876,24 @@ this.createjs = this.createjs || {}; /** * A DynamicsCompressorNode, which is used to improve sound quality and prevent audio distortion. * It is connected to context.destination. + * + * Can be accessed by advanced users through createjs.Sound.activePlugin.dynamicsCompressorNode. * @property dynamicsCompressorNode * @type {AudioNode} */ p.dynamicsCompressorNode = null; /** - * A GainNode for controlling master _volume. It is connected to {{#crossLink "WebAudioPlugin/dynamicsCompressorNode:property"}}{{/crossLink}}. + * A GainNode for controlling master volume. It is connected to {{#crossLink "WebAudioPlugin/dynamicsCompressorNode:property"}}{{/crossLink}}. + * + * Can be accessed by advanced users through createjs.Sound.activePlugin.gainNode. * @property gainNode * @type {AudioGainNode} */ p.gainNode = null; /** - * An object hash used internally to store ArrayBuffers, indexed by the source URI used to load it. This + * An object hash used internally to store ArrayBuffers, indexed by the source URI used to load it. This * prevents having to load and decode audio files more than once. If a load has been started on a file, * arrayBuffers[src] will be set to true. Once load is complete, it is set the the loaded * ArrayBuffer instance. @@ -2943,8 +2913,13 @@ this.createjs = this.createjs || {}; this._arrayBuffers = {}; this.context = s.context; - this.gainNode = s.gainNode; - this.dynamicsCompressorNode = s.dynamicsCompressorNode; + this._panningModel = s._panningModel; + + // set up AudioNodes that all of our source audio will connect to + this.dynamicsCompressorNode = this.context.createDynamicsCompressor(); + this.dynamicsCompressorNode.connect(this.context.destination); + this.gainNode = this.context.createGain(); + this.gainNode.connect(this.dynamicsCompressorNode); }; /** @@ -2958,11 +2933,9 @@ this.createjs = this.createjs || {}; * @return {Object} A result object, containing a "tag" for preloading purposes. */ p.register = function (src, instances) { - this._arrayBuffers[src] = true; // This is needed for PreloadJS - var tag = new createjs.WebAudioPlugin.Loader(src, this); - return { - tag:tag - }; + this._arrayBuffers[src] = true; + var loader = {tag: new createjs.WebAudioPlugin.Loader(src, this)}; + return loader; }; /** @@ -3021,19 +2994,18 @@ this.createjs = this.createjs || {}; * @method _handlePreloadComplete * @protected */ - p._handlePreloadComplete = function () { - //LM: I would recommend having the Loader include an "event" in the onload, and properly binding this callback. - createjs.Sound._sendFileLoadEvent(this.src); // fire event or callback on Sound - // note "this" will reference Loader object + p._handlePreloadComplete = function (loader) { + createjs.Sound._sendFileLoadEvent(loader.src); + loader.cleanUp(); }; /** * Internally preload a sound. Loading uses XHR2 to load an array buffer for use with WebAudio. * @method preload * @param {String} src The sound URI to load. - * @param {Object} instance Not used in this plugin. + * @param {Object} tag Not used in this plugin. */ - p.preload = function (src, instance) { + p.preload = function (src, tag) { this._arrayBuffers[src] = true; var loader = new createjs.WebAudioPlugin.Loader(src, this); loader.onload = this._handlePreloadComplete; @@ -3044,13 +3016,13 @@ this.createjs = this.createjs || {}; * Create a sound instance. If the sound has not been preloaded, it is internally preloaded here. * @method create * @param {String} src The sound source to use. + * @param {Number} startTime Audio sprite property used to apply an offset, in milliseconds. + * @param {Number} duration Audio sprite property used to set the time the clip plays for, in milliseconds. * @return {SoundInstance} A sound instance for playback and control. */ - p.create = function (src) { - if (!this.isPreloadStarted(src)) { - this.preload(src); - } - return new createjs.WebAudioPlugin.SoundInstance(src, this); + p.create = function (src, startTime, duration) { + if (!this.isPreloadStarted(src)) {this.preload(src);} + return new createjs.WebAudioPlugin.SoundInstance(src, startTime, duration, this); }; /** @@ -3116,7 +3088,6 @@ this.createjs = this.createjs || {}; * for control by the user. * *

    Example

    - * * var myInstance = createjs.Sound.play("myAssetPath/mySrcFile.mp3"); * * A number of additional parameters provide a quick way to determine how a sound is played. Please see the Sound @@ -3144,15 +3115,18 @@ this.createjs = this.createjs || {}; * * @class SoundInstance * @param {String} src The path to and file name of the sound. + * @param {Number} startTime Audio sprite property used to apply an offset, in milliseconds. + * @param {Number} duration Audio sprite property used to set the time the clip plays for, in milliseconds. * @param {Object} owner The plugin instance that created this SoundInstance. * @extends EventDispatcher * @constructor */ - function SoundInstance(src, owner) { - this._init(src, owner); + function SoundInstance(src, startTime, duration, owner) { + this._init(src, startTime, duration, owner); } var p = SoundInstance.prototype = new createjs.EventDispatcher(); + p.constructor = SoundInstance; /** * The source of the sound. @@ -3199,14 +3173,12 @@ this.createjs = this.createjs || {}; p._offset = 0; /** - * The time in milliseconds before the sound starts. - * Note this is handled by {{#crossLink "Sound"}}{{/crossLink}}. - * @property _delay + * Audio sprite property used to determine the starting offset. * @type {Number} - * @default 0 + * @default null * @protected */ - p._delay = 0; // OJR remove this property from SoundInstance as it is not used here? + p._startTime = 0; /** * The volume of the sound, between 0 and 1. @@ -3221,8 +3193,7 @@ this.createjs = this.createjs || {}; * @default 1 */ p._volume = 1; - // IE8 has Object.defineProperty, but only for DOM objects, so check if fails to suppress errors - try { + if (createjs.definePropertySupported) { Object.defineProperty(p, "volume", { get: function() { return this._volume; @@ -3234,9 +3205,7 @@ this.createjs = this.createjs || {}; this._updateVolume(); } }); - } catch (e) { - // dispatch message or error? - }; + } /** * The pan of the sound, between -1 (left) and 1 (right). Note that pan is not supported by HTML Audio. @@ -3250,8 +3219,7 @@ this.createjs = this.createjs || {}; * @default 0 */ p._pan = 0; - // IE8 has Object.defineProperty, but only for DOM objects, so check if fails to suppress errors - try { + if (createjs.definePropertySupported) { Object.defineProperty(p, "pan", { get: function() { return this._pan; @@ -3265,10 +3233,7 @@ this.createjs = this.createjs || {}; this.panNode.setPosition(value, 0, -0.5); // z need to be -0.5 otherwise the sound only plays in left, right, or center } }); - } catch (e) { - // dispatch message or error? - }; - + } /** * The length of the audio clip, in milliseconds. @@ -3282,16 +3247,35 @@ this.createjs = this.createjs || {}; /** * The number of play loops remaining. Negative values will loop infinitely. - * @property _remainingLoops + * + * @property loop * @type {Number} * @default 0 - * @protected + * @public */ p._remainingLoops = 0; + if (createjs.definePropertySupported) { + Object.defineProperty(p, "loop", { + get: function() { + return this._remainingLoops; + }, + set: function(value) { + // remove looping + if (this._remainingLoops != 0 && value == 0) { + this._sourceNodeNext = this._cleanUpAudioNode(this._sourceNodeNext); + } + // add looping + if (this._remainingLoops == 0 && value != 0) { + this._sourceNodeNext = this._createAndPlayAudioNode(this._playbackStartTime, 0); + } + this._remainingLoops = value; + } + }); + } /** * A Timeout created by {{#crossLink "Sound"}}{{/crossLink}} when this SoundInstance is played with a delay. - * This allows SoundInstance to remove the delay if stop or pause or cleanup are called before playback begins. + * This allows SoundInstance to remove the delay if stop, pause, or cleanup are called before playback begins. * @property _delayTimeoutId * @type {timeoutVariable} * @default null @@ -3376,13 +3360,13 @@ this.createjs = this.createjs || {}; /** * WebAudioPlugin only. * Time audio started playback, in seconds. Used to handle set position, get position, and resuming from paused. - * @property _startTime + * @property _playbackStartTime * @type {Number} * @default 0 * @protected * @since 0.4.0 */ - p._startTime = 0; + p._playbackStartTime = 0; // Proxies, make removing listeners easier. p._endedHandler = null; @@ -3432,43 +3416,6 @@ this.createjs = this.createjs || {}; * @since 0.4.0 */ - //TODO: Deprecated - /** - * REMOVED. Use {{#crossLink "EventDispatcher/addEventListener"}}{{/crossLink}} and the {{#crossLink "SoundInstance/succeeded:event"}}{{/crossLink}} - * event. - * @property onPlaySucceeded - * @type {Function} - * @deprecated Use addEventListener and the "succeeded" event. - */ - /** - * REMOVED. Use {{#crossLink "EventDispatcher/addEventListener"}}{{/crossLink}} and the {{#crossLink "SoundInstance/interrupted:event"}}{{/crossLink}} - * event. - * @property onPlayInterrupted - * @type {Function} - * @deprecated Use addEventListener and the "interrupted" event. - */ - /** - * REMOVED. Use {{#crossLink "EventDispatcher/addEventListener"}}{{/crossLink}} and the {{#crossLink "SoundInstance/failed:event"}}{{/crossLink}} - * event. - * @property onPlayFailed - * @type {Function} - * @deprecated Use addEventListener and the "failed" event. - */ - /** - * REMOVED. Use {{#crossLink "EventDispatcher/addEventListener"}}{{/crossLink}} and the {{#crossLink "SoundInstance/complete:event"}}{{/crossLink}} - * event. - * @property onComplete - * @type {Function} - * @deprecated Use addEventListener and the "complete" event. - */ - /** - * REMOVED. Use {{#crossLink "EventDispatcher/addEventListener"}}{{/crossLink}} and the {{#crossLink "SoundInstance/loop:event"}}{{/crossLink}} - * event. - * @property onLoop - * @type {Function} - * @deprecated Use addEventListener and the "loop" event. - */ - /** * A helper method that dispatches all events for SoundInstance. * @method _sendEvent @@ -3485,22 +3432,24 @@ this.createjs = this.createjs || {}; * Initialize the SoundInstance. This is called from the constructor. * @method _init * @param {string} src The source of the audio. + * @param {Number} startTime Audio sprite property used to apply an offset, in milliseconds. + * @param {Number} duration Audio sprite property used to set the time the clip plays for, in milliseconds. * @param {Class} owner The plugin that created this instance. * @protected */ - p._init = function (src, owner) { - this._owner = owner; + p._init = function (src, startTime, duration, owner) { this.src = src; + this._startTime = startTime * 0.001 || 0; // convert ms to s as web audio handles everything in seconds + this._duration = duration || 0; + this._owner = owner; this.gainNode = this._owner.context.createGain(); - this.panNode = this._owner.context.createPanner(); //TODO test how this affects when we have mono audio + this.panNode = this._owner.context.createPanner(); this.panNode.panningModel = this._owner._panningModel; this.panNode.connect(this.gainNode); - if (this._owner.isPreloadComplete(this.src)) { - this._duration = this._owner._arrayBuffers[this.src].duration * 1000; - } + if (this._owner.isPreloadComplete(this.src) && !this._duration) {this._duration = this._owner._arrayBuffers[this.src].duration * 1000;} this._endedHandler = createjs.proxy(this._handleSoundComplete, this); }; @@ -3516,19 +3465,14 @@ this.createjs = this.createjs || {}; this._sourceNodeNext = this._cleanUpAudioNode(this._sourceNodeNext); } - if (this.gainNode.numberOfOutputs != 0) { - this.gainNode.disconnect(0); - } // this works because we only have one connection, and it returns 0 if we've already disconnected it. + if (this.gainNode.numberOfOutputs != 0) {this.gainNode.disconnect(0);} // OJR there appears to be a bug that this doesn't always work in webkit (Chrome and Safari). According to the documentation, this should work. clearTimeout(this._delayTimeoutId); // clear timeout that plays delayed sound clearTimeout(this._soundCompleteTimeout); // clear timeout that triggers sound complete - this._startTime = 0; // This is used by getPosition + this._playbackStartTime = 0; // This is used by getPosition - if (window.createjs == null) { - return; - } createjs.Sound._playFinished(this); }; @@ -3543,8 +3487,8 @@ this.createjs = this.createjs || {}; p._cleanUpAudioNode = function(audioNode) { if(audioNode) { audioNode.stop(0); - audioNode.disconnect(this.panNode); - audioNode = null; // release reference so Web Audio can handle removing references and garbage collection + audioNode.disconnect(0); + audioNode = null; } return audioNode; }; @@ -3567,11 +3511,8 @@ this.createjs = this.createjs || {}; * @protected */ p._handleSoundReady = function (event) { - if (window.createjs == null) { - return; - } - - if ((this._offset*1000) > this.getDuration()) { // converting offset to ms + if (!this._duration) {this._duration = this._owner._arrayBuffers[this.src].duration * 1000;} // NOTE *1000 because WebAudio reports everything in seconds but js uses milliseconds + if ((this._offset*1000) > this._duration) { this.playFailed(); return; } else if (this._offset < 0) { // may not need this check if play ignores negative values, this is not specified in the API http://www.w3.org/TR/webaudio/#AudioBufferSourceNode @@ -3583,15 +3524,14 @@ this.createjs = this.createjs || {}; this.gainNode.connect(this._owner.gainNode); // this line can cause a memory leak. Nodes need to be disconnected from the audioDestination or any sequence that leads to it. - var dur = this._owner._arrayBuffers[this.src].duration; + var dur = this._duration * 0.001; this.sourceNode = this._createAndPlayAudioNode((this._owner.context.currentTime - dur), this._offset); - this._duration = dur * 1000; // NOTE *1000 because WebAudio reports everything in seconds but js uses milliseconds - this._startTime = this.sourceNode.startTime - this._offset; + this._playbackStartTime = this.sourceNode.startTime - this._offset; this._soundCompleteTimeout = setTimeout(this._endedHandler, (dur - this._offset) * 1000); if(this._remainingLoops != 0) { - this._sourceNodeNext = this._createAndPlayAudioNode(this._startTime, 0); + this._sourceNodeNext = this._createAndPlayAudioNode(this._playbackStartTime, 0); } }; @@ -3608,9 +3548,9 @@ this.createjs = this.createjs || {}; var audioNode = this._owner.context.createBufferSource(); audioNode.buffer = this._owner._arrayBuffers[this.src]; audioNode.connect(this.panNode); - var currentTime = this._owner.context.currentTime; - audioNode.startTime = startTime + audioNode.buffer.duration; //currentTime + audioNode.buffer.duration - (currentTime - startTime); - audioNode.start(audioNode.startTime, offset, audioNode.buffer.duration - offset); + var dur = this._duration * 0.001; + audioNode.startTime = startTime + dur; + audioNode.start(audioNode.startTime, offset+this._startTime, dur - offset); return audioNode; }; @@ -3624,6 +3564,8 @@ this.createjs = this.createjs || {}; * myInstance.play({offset:1, loop:2, pan:0.5}); // options as object properties * myInstance.play(createjs.Sound.INTERRUPT_ANY); // options as parameters * + * Note that if this sound is already playing, this call will do nothing. + * * @method play * @param {String | Object} [interrupt="none"|options] How to interrupt any currently playing instances of audio with the same source, * if the maximum number of instances of the sound are already playing. Values are defined as INTERRUPT_TYPE @@ -3639,6 +3581,20 @@ this.createjs = this.createjs || {}; * for HTML Audio. */ p.play = function (interrupt, delay, offset, loop, volume, pan) { + if (this.playState == createjs.Sound.PLAY_SUCCEEDED) { + if (interrupt instanceof Object) { + offset = interrupt.offset; + loop = interrupt.loop; + volume = interrupt.volume; + pan = interrupt.pan; + } + if (offset != null) { this.setPosition(offset) } + if (loop != null) { this.loop = loop; } + if (volume != null) { this.setVolume(volume); } + if (pan != null) { this.setPan(pan); } + if (this._paused) { this.resume(); } + return; + } this._cleanUp(); createjs.Sound._playInstance(this, interrupt, delay, offset, loop, volume, pan); }; @@ -3654,15 +3610,7 @@ this.createjs = this.createjs || {}; * @protected */ p._beginPlaying = function (offset, loop, volume, pan) { - if (window.createjs == null) { - return; - } - - if (!this.src) { - return; - } - - this._offset = offset / 1000; //convert ms to sec + this._offset = offset * 0.001; //convert ms to sec this._remainingLoops = loop; this.volume = volume; this.pan = pan; @@ -3689,22 +3637,19 @@ this.createjs = this.createjs || {}; * @return {Boolean} If the pause call succeeds. This will return false if the sound isn't currently playing. */ p.pause = function () { - if (!this._paused && this.playState == createjs.Sound.PLAY_SUCCEEDED) { - this.paused = this._paused = true; + if (this._paused || this.playState != createjs.Sound.PLAY_SUCCEEDED) {return false;} - this._offset = this._owner.context.currentTime - this._startTime; // this allows us to restart the sound at the same point in playback - this.sourceNode = this._cleanUpAudioNode(this.sourceNode); - this.sourceNodeNext = this._cleanUpAudioNode(this._sourceNodeNext); + this.paused = this._paused = true; - if (this.gainNode.numberOfOutputs != 0) { - this.gainNode.disconnect(); - } // this works because we only have one connection, and it returns 0 if we've already disconnected it. + this._offset = this._owner.context.currentTime - this._playbackStartTime; // this allows us to restart the sound at the same point in playback + this.sourceNode = this._cleanUpAudioNode(this.sourceNode); + this._sourceNodeNext = this._cleanUpAudioNode(this._sourceNodeNext); - clearTimeout(this._delayTimeoutId); // clear timeout that plays delayed sound - clearTimeout(this._soundCompleteTimeout); // clear timeout that triggers sound complete - return true; - } - return false; + if (this.gainNode.numberOfOutputs != 0) {this.gainNode.disconnect(0);} + + clearTimeout(this._delayTimeoutId); + clearTimeout(this._soundCompleteTimeout); + return true; }; /** @@ -3721,10 +3666,8 @@ this.createjs = this.createjs || {}; * @return {Boolean} If the resume call succeeds. This will return false if called on a sound that is not paused. */ p.resume = function () { - if (!this._paused) { - return false; - } - this._handleSoundReady(null); + if (!this._paused) {return false;} + this._handleSoundReady(); return true; }; @@ -3764,23 +3707,20 @@ this.createjs = this.createjs || {}; */ p.setVolume = function (value) { this.volume = value; - return true; // This is always true because even if the volume is not updated, the value is set + return true; }; /** * Internal function used to update the volume based on the instance volume, master volume, instance mute value, * and master mute value. * @method _updateVolume - * @return {Boolean} if the volume was updated. * @protected */ p._updateVolume = function () { var newVolume = this._muted ? 0 : this._volume; if (newVolume != this.gainNode.gain.value) { this.gainNode.gain.value = newVolume; - return true; } - return false; }; /** @@ -3810,9 +3750,7 @@ this.createjs = this.createjs || {}; * @since 0.4.0 */ p.setMute = function (value) { - if (value == null || value == undefined) { - return false; - } + if (value == null) {return false;} this._muted = value; this._updateVolume(); @@ -3852,6 +3790,7 @@ this.createjs = this.createjs || {}; p.setPan = function (value) { this.pan = value; // Unfortunately panner does not give us a way to access this after it is set http://www.w3.org/TR/webaudio/#AudioPannerNode if(this.pan != value) {return false;} + return true; }; /** @@ -3885,7 +3824,7 @@ this.createjs = this.createjs || {}; if (this._paused || this.sourceNode == null) { var pos = this._offset; } else { - var pos = this._owner.context.currentTime - this._startTime; + var pos = this._owner.context.currentTime - this._playbackStartTime; } return pos * 1000; // pos in seconds * 1000 to give milliseconds @@ -3903,7 +3842,7 @@ this.createjs = this.createjs || {}; * @param {Number} value The position to place the playhead, in milliseconds. */ p.setPosition = function (value) { - this._offset = value / 1000; // convert milliseconds to seconds + this._offset = value * 0.001; // convert milliseconds to seconds if (this.sourceNode && this.playState == createjs.Sound.PLAY_SUCCEEDED) { // we need to stop this sound from continuing to play, as we need to recreate the sourceNode to change position @@ -3912,9 +3851,7 @@ this.createjs = this.createjs || {}; clearTimeout(this._soundCompleteTimeout); // clear timeout that triggers sound complete } // NOTE we cannot just call cleanup because it also calls the Sound function _playFinished which releases this instance in SoundChannel - if (!this._paused && this.playState == createjs.Sound.PLAY_SUCCEEDED) { - this._handleSoundReady(null); - } + if (!this._paused && this.playState == createjs.Sound.PLAY_SUCCEEDED) {this._handleSoundReady();} return true; }; @@ -3955,21 +3892,18 @@ this.createjs = this.createjs || {}; if(this._sourceNodeNext) { // this can be set to null, but this should not happen when looping this._cleanUpAudioNode(this.sourceNode); this.sourceNode = this._sourceNodeNext; - this._startTime = this.sourceNode.startTime; - this._sourceNodeNext = this._createAndPlayAudioNode(this._startTime, 0); + this._playbackStartTime = this.sourceNode.startTime; + this._sourceNodeNext = this._createAndPlayAudioNode(this._playbackStartTime, 0); this._soundCompleteTimeout = setTimeout(this._endedHandler, this._duration); } else { - this._handleSoundReady(null); + this._handleSoundReady(); } this._sendEvent("loop"); return; } - if (window.createjs == null) { - return; - } this._cleanUp(); this.playState = createjs.Sound.PLAY_FINISHED; this._sendEvent("complete"); @@ -3977,9 +3911,6 @@ this.createjs = this.createjs || {}; // Play has failed, which can happen for a variety of reasons. p.playFailed = function () { - if (window.createjs == null) { - return; - } this._cleanUp(); this.playState = createjs.Sound.PLAY_FAILED; this._sendEvent("failed"); @@ -4009,6 +3940,7 @@ this.createjs = this.createjs || {}; } var p = Loader.prototype; + p.constructor = Loader; // the request object for or XHR2 request p.request = null; @@ -4023,13 +3955,6 @@ this.createjs = this.createjs || {}; */ p.src = null; - /** - * The original source of the sound, before it is altered with a basePath. - * #property src - * @type {String} - */ - p.originalSrc = null; - /** * The decoded AudioBuffer array that is returned when loading is complete. * #property result @@ -4054,17 +3979,16 @@ this.createjs = this.createjs || {}; p.onprogress = null; /** - * The callback that fires if the load hits an error. - * #property onError + * The callback that fires if the load hits an error. This follows HTML tag naming. + * #property onerror * @type {Method} * @protected */ - p.onError = null; + p.onerror = null; // constructor p._init = function (src, owner) { this.src = src; - this.originalSrc = src; this.owner = owner; }; @@ -4074,16 +3998,13 @@ this.createjs = this.createjs || {}; * @param {String} src The path to the sound. */ p.load = function (src) { - if (src != null) { - // TODO does this need to set this.originalSrc - this.src = src; - } + if (src != null) {this.src = src;} this.request = new XMLHttpRequest(); this.request.open("GET", this.src, true); this.request.responseType = "arraybuffer"; this.request.onload = createjs.proxy(this.handleLoad, this); - this.request.onError = createjs.proxy(this.handleError, this); + this.request.onerror = createjs.proxy(this.handleError, this); this.request.onprogress = createjs.proxy(this.handleProgress, this); this.request.send(); @@ -4095,13 +4016,15 @@ this.createjs = this.createjs || {}; * Note: this is not a public API, but is used to allow preloaders to subscribe to load * progress as if this is an HTML audio tag. This reason is why this still uses a callback instead of an event. * #method handleProgress - * @param {Number} loaded The loaded amount. - * @param {Number} total The total amount. + * @param {event} event Progress event that gives event.loaded and event.total if server is configured correctly * @protected */ - p.handleProgress = function (loaded, total) { - this.progress = loaded / total; - this.onprogress != null && this.onprogress({loaded:loaded, total:total, progress:this.progress}); + p.handleProgress = function (event) { + if (!event || event.loaded > 0 && event.total == 0) { + return; // Sometimes we get no "total", so just ignore the progress event. + } + this.progress = event.loaded / event.total; + this.onprogress && this.onprogress({loaded:event.loaded, total:event.total, progress:this.progress}); }; /** @@ -4123,9 +4046,8 @@ this.createjs = this.createjs || {}; p.handleAudioDecoded = function (decodedAudio) { this.progress = 1; this.result = decodedAudio; - this.src = this.originalSrc; this.owner.addPreloadResults(this.src, this.result); - this.onload && this.onload(); + this.onload && this.onload(this); }; /** @@ -4138,6 +4060,23 @@ this.createjs = this.createjs || {}; this.onerror && this.onerror(evt); }; + /** + * Remove all external references from loader + * #method cleanUp + */ + p.cleanUp = function () { + if(!this.request) {return;} + this.src = null; + this.owner = null; + this.request.onload = null; + this.request.onerror = null; + this.request.onprogress = null; + this.request = null; + this.onload = null; + this.onprogress = null; + this.onerror = null; + }; + p.toString = function () { return "[WebAudioPlugin Loader]"; }; @@ -4197,14 +4136,16 @@ this.createjs = this.createjs || {}; * tags are precreated to allow Chrome to load them. Please use {{#crossLink "Sound.MAX_INSTANCES"}}{{/crossLink}} as * a guide to how many total audio tags you can safely use in all browsers. * - * IE 9 html limitations
    + * IE html limitations
    *
    • There is a delay in applying volume changes to tags that occurs once playback is started. So if you have * muted all sounds, they will all play during this delay until the mute applies internally. This happens regardless of * when or how you apply the volume change, as the tag seems to need to play to apply it.
    • *
    • MP3 encoding will not always work for audio tags if it's not default. We've found default encoding with * 64kbps works.
    • + *
    • Occasionally very short samples will get cut off.
    • *
    • There is a limit to how many audio tags you can load and play at once, which appears to be determined by - * hardware and browser settings. See {{#crossLink "HTMLAudioPlugin.MAX_INSTANCES"}}{{/crossLink}} for a safe estimate.
    + * hardware and browser settings. See {{#crossLink "HTMLAudioPlugin.MAX_INSTANCES"}}{{/crossLink}} for a safe estimate. + * Note that audio sprites can be used as a solution to this issue. * * Safari limitations
    *
    • Safari requires Quicktime to be installed for audio playback.
    @@ -4216,6 +4157,7 @@ this.createjs = this.createjs || {}; *
  • can not preload or autoplay the audio
  • *
  • can not cache the audio
  • *
  • can not play the audio except inside a user initiated event.
  • + *
  • audio sprites can be used to mitigate some of these issues and are strongly recommended on iOS
  • * * * Android Native Browser limitations
    @@ -4291,6 +4233,17 @@ this.createjs = this.createjs || {}; */ s._AUDIO_STALLED = "stalled"; + /** + * Event constant for the "timeupdate" event for cleaner code. Utilized for looping audio sprites. + * This event callsback ever 15 to 250ms and can be dropped by the browser for performance. + * @property _TIME_UPDATE + * @type {String} + * @default timeupdate + * @static + * @protected + */ + s._TIME_UPDATE = "timeupdate"; + /** * The capabilities of the plugin. This is generated via the the SoundInstance {{#crossLink "HTMLAudioPlugin/_generateCapabilities"}}{{/crossLink}} * method. Please see the Sound {{#crossLink "Sound/getCapabilities"}}{{/crossLink}} method for an overview of all @@ -4303,9 +4256,7 @@ this.createjs = this.createjs || {}; s._capabilities = null; /** - * Allows users to enable HTML audio on IOS, which is disabled by default. - * Note this needs to be set before HTMLAudioPlugin is registered with SoundJS. - * This is not recommend because of severe limitations on IOS devices including: + * Deprecated now that we have audio sprite support. Audio sprites are strongly recommend on iOS. *
  • it can only have one <audio> tag
  • *
  • can not preload or autoplay the audio
  • *
  • can not cache the audio
  • @@ -4314,6 +4265,7 @@ this.createjs = this.createjs || {}; * @property enableIOS * @type {Boolean} * @default false + * @deprecated */ s.enableIOS = false; @@ -4325,14 +4277,8 @@ this.createjs = this.createjs || {}; * @static */ s.isSupported = function () { - if (createjs.Sound.BrowserDetect.isIOS && !s.enableIOS) { - return false; - } s._generateCapabilities(); - var t = s.tag; // OJR do we still need this check, when cap will already be null if this is the case - if (t == null || s._capabilities == null) { - return false; - } + if (s._capabilities == null) {return false;} return true; }; @@ -4344,13 +4290,9 @@ this.createjs = this.createjs || {}; * @protected */ s._generateCapabilities = function () { - if (s._capabilities != null) { - return; - } - var t = s.tag = document.createElement("audio"); - if (t.canPlayType == null) { - return null; - } + if (s._capabilities != null) {return;} + var t = document.createElement("audio"); + if (t.canPlayType == null) {return null;} s._capabilities = { panning:true, @@ -4369,6 +4311,7 @@ this.createjs = this.createjs || {}; } var p = HTMLAudioPlugin.prototype; + p.constructor = HTMLAudioPlugin; // doc'd above p._capabilities = null; @@ -4383,7 +4326,7 @@ this.createjs = this.createjs || {}; p._audioSources = null; /** - * The default number of instances to allow. Passed back to {{#crossLink "Sound"}}{{/crossLink}} when a source + * The default number of instances to allow. Used by {{#crossLink "Sound"}}{{/crossLink}} when a source * is registered using the {{#crossLink "Sound/register"}}{{/crossLink}} method. This is only used if * a value is not provided. * @@ -4395,9 +4338,6 @@ this.createjs = this.createjs || {}; */ p.defaultNumChannels = 2; - // Proxies, make removing listeners easier. - p.loadedHandler = null; - /** * An initialization function run by the constructor * @method _init @@ -4422,52 +4362,17 @@ this.createjs = this.createjs || {}; this._audioSources[src] = true; // Note this does not mean preloading has started var channel = createjs.HTMLAudioPlugin.TagPool.get(src); var tag = null; - var l = instances || this.defaultNumChannels; - for (var i = 0; i < l; i++) { // OJR should we be enforcing s.MAX_INSTANCES here? Does the chrome bug still exist, or can we change this code? + var l = instances; + for (var i = 0; i < l; i++) { tag = this._createTag(src); channel.add(tag); } - tag.id = src; // co-opting id as we need a way to store original src in case it is changed before loading - this.loadedHandler = createjs.proxy(this._handleTagLoad, this); // we need this bind to be able to remove event listeners - tag.addEventListener && tag.addEventListener("canplaythrough", this.loadedHandler); - if(tag.onreadystatechange == null) { - tag.onreadystatechange = this.loadedHandler; - } else { - var f = tag.onreadystatechange; - // OJR will this lose scope? - tag.onreadystatechange = function() { - f(); - this.loadedHandler(); - } - } - return { - tag:tag, // Return one instance for preloading purposes - numChannels:l // The default number of channels to make for this Sound or the passed in value + tag:tag // Return one instance for preloading purposes }; }; - // TODO remove this when | approach is removed - /** - * Deprecated as this will not be required with new approach to basePath. - * Checks if src was changed on tag used to create instances in TagPool before loading - * Currently PreloadJS does this when a basePath is set, so we are replicating that behavior for internal preloading. - * @method _handleTagLoad - * @param event - * @protected - * @deprecated - */ - p._handleTagLoad = function(event) { - // cleanup and so we don't send the event more than once - event.target.removeEventListener && event.target.removeEventListener("canplaythrough", this.loadedHandler); - event.target.onreadystatechange = null; - - if (event.target.src == event.target.id) { return; } - // else src has changed before loading, and we need to make the change to TagPool because we pre create tags - createjs.HTMLAudioPlugin.TagPool.checkSrc(event.target.id); - }; - /** * Create an HTML audio tag. * @method _createTag @@ -4503,7 +4408,7 @@ this.createjs = this.createjs || {}; * @since 0.4.1 */ p.removeAllSounds = function () { - this._audioSources = {}; // this drops all references, in theory freeing them for garbage collection + this._audioSources = {}; createjs.HTMLAudioPlugin.TagPool.removeAll(); }; @@ -4511,9 +4416,11 @@ this.createjs = this.createjs || {}; * Create a sound instance. If the sound has not been preloaded, it is internally preloaded here. * @method create * @param {String} src The sound source to use. + * @param {Number} startTime Audio sprite property used to apply an offset, in milliseconds. + * @param {Number} duration Audio sprite property used to set the time the clip plays for, in milliseconds. * @return {SoundInstance} A sound instance for playback and control. */ - p.create = function (src) { + p.create = function (src, startTime, duration) { // if this sound has not be registered, create a tag and preload it if (!this.isPreloadStarted(src)) { var channel = createjs.HTMLAudioPlugin.TagPool.get(src); @@ -4523,7 +4430,7 @@ this.createjs = this.createjs || {}; this.preload(src, {tag:tag}); } - return new createjs.HTMLAudioPlugin.SoundInstance(src, this); + return new createjs.HTMLAudioPlugin.SoundInstance(src, startTime, duration, this); }; /** @@ -4541,12 +4448,12 @@ this.createjs = this.createjs || {}; * Internally preload a sound. * @method preload * @param {String} src The sound URI to load. - * @param {Object} instance An object containing a tag property that is an HTML audio tag used to load src. + * @param {Object} tag An HTML audio tag used to load src. * @since 0.4.0 */ - p.preload = function (src, instance) { + p.preload = function (src, tag) { this._audioSources[src] = true; - new createjs.HTMLAudioPlugin.Loader(src, instance.tag); + new createjs.HTMLAudioPlugin.Loader(src, tag); }; p.toString = function () { @@ -4563,11 +4470,12 @@ this.createjs = this.createjs || {}; // NOTE Documentation for the SoundInstance class in WebAudioPlugin file. Each plugin generates a SoundInstance that // follows the same interface. - function SoundInstance(src, owner) { - this._init(src, owner); + function SoundInstance(src, startTime, duration, owner) { + this._init(src, startTime, duration, owner); } var p = SoundInstance.prototype = new createjs.EventDispatcher(); + p.constructor = SoundInstance; p.src = null; p.uniqueId = -1; @@ -4575,10 +4483,9 @@ this.createjs = this.createjs || {}; p._owner = null; p.loaded = false; p._offset = 0; - p._delay = 0; + p._startTime = 0; p._volume = 1; - // IE8 has Object.defineProperty, but only for DOM objects, so check if fails to suppress errors - try { + if (createjs.definePropertySupported) { Object.defineProperty(p, "volume", { get: function() { return this._volume; @@ -4590,12 +4497,33 @@ this.createjs = this.createjs || {}; this._updateVolume(); } }); - } catch (e) { - // dispatch message or error? - }; + } p.pan = 0; p._duration = 0; + p._audioSpriteStopTime = null; // HTMLAudioPlugin only p._remainingLoops = 0; + if (createjs.definePropertySupported) { + Object.defineProperty(p, "loop", { + get: function() { + return this._remainingLoops; + }, + set: function(value) { + if (this.tag != null) { + // remove looping + if (this._remainingLoops != 0 && value == 0) { + this.tag.loop = false; + this.tag.removeEventListener(createjs.HTMLAudioPlugin._AUDIO_SEEKED, this.loopHandler, false); + } + // add looping + if (this._remainingLoops == 0 && value != 0) { + this.tag.addEventListener(createjs.HTMLAudioPlugin._AUDIO_SEEKED, this.loopHandler, false); + this.tag.loop = true; + } + } + this._remainingLoops = value; + } + }); + } p._delayTimeoutId = null; p.tag = null; p._muted = false; @@ -4606,16 +4534,25 @@ this.createjs = this.createjs || {}; p._endedHandler = null; p._readyHandler = null; p._stalledHandler = null; + p._audioSpriteEndHandler = null; p.loopHandler = null; // Constructor - p._init = function (src, owner) { + p._init = function (src, startTime, duration, owner) { this.src = src; + this._startTime = startTime || 0; // convert ms to s as web audio handles everything in seconds + if (duration) { + this._duration = duration; + this._audioSpriteStopTime = (startTime + duration) * 0.001; + } else { + this._duration = createjs.HTMLAudioPlugin.TagPool.getDuration(this.src); + } this._owner = owner; this._endedHandler = createjs.proxy(this._handleSoundComplete, this); this._readyHandler = createjs.proxy(this._handleSoundReady, this); this._stalledHandler = createjs.proxy(this._handleSoundStalled, this); + this.__audioSpriteEndHandler = createjs.proxy(this._handleAudioSpriteLoop, this); this.loopHandler = createjs.proxy(this.handleSoundLoop, this); }; @@ -4628,11 +4565,14 @@ this.createjs = this.createjs || {}; var tag = this.tag; if (tag != null) { tag.pause(); + this.tag.loop = false; tag.removeEventListener(createjs.HTMLAudioPlugin._AUDIO_ENDED, this._endedHandler, false); tag.removeEventListener(createjs.HTMLAudioPlugin._AUDIO_READY, this._readyHandler, false); tag.removeEventListener(createjs.HTMLAudioPlugin._AUDIO_SEEKED, this.loopHandler, false); + tag.removeEventListener(createjs.HTMLAudioPlugin._TIME_UPDATE, this.__audioSpriteEndHandler, false); + try { - tag.currentTime = 0; + tag.currentTime = this._startTime; } catch (e) { } // Reset Position createjs.HTMLAudioPlugin.TagPool.setInstance(this.src, tag); @@ -4640,16 +4580,11 @@ this.createjs = this.createjs || {}; } clearTimeout(this._delayTimeoutId); - if (window.createjs == null) { - return; - } createjs.Sound._playFinished(this); }; p._interrupt = function () { - if (this.tag == null) { - return; - } + if (this.tag == null) {return;} this.playState = createjs.Sound.PLAY_INTERRUPTED; this._cleanUp(); this.paused = this._paused = false; @@ -4658,14 +4593,25 @@ this.createjs = this.createjs || {}; // Public API p.play = function (interrupt, delay, offset, loop, volume, pan) { - this._cleanUp(); //LM: Is this redundant? + if (this.playState == createjs.Sound.PLAY_SUCCEEDED) { + if (interrupt instanceof Object) { + offset = interrupt.offset; + loop = interrupt.loop; + volume = interrupt.volume; + pan = interrupt.pan; + } + if (offset != null) { this.setPosition(offset) } + if (loop != null) { this.loop = loop; } + if (volume != null) { this.setVolume(volume); } + if (pan != null) { this.setPan(pan); } + if (this._paused) { this.resume(); } + return; + } + this._cleanUp(); createjs.Sound._playInstance(this, interrupt, delay, offset, loop, volume, pan); }; p._beginPlaying = function (offset, loop, volume, pan) { - if (window.createjs == null) { - return -1; - } var tag = this.tag = createjs.HTMLAudioPlugin.TagPool.getInstance(this.src); if (tag == null) { this.playFailed(); @@ -4677,8 +4623,7 @@ this.createjs = this.createjs || {}; // Reset this instance. this._offset = offset; this.volume = volume; - this.pan = pan; // not pan has no effect - this._updateVolume(); // note this will set for mute and _masterMute + this._updateVolume(); this._remainingLoops = loop; if (tag.readyState !== 4) { @@ -4697,55 +4642,47 @@ this.createjs = this.createjs || {}; // Note: Sounds stall when trying to begin playback of a new audio instance when the existing instances // has not loaded yet. This doesn't mean the sound will not play. p._handleSoundStalled = function (event) { - this._cleanUp(); // OJR NOTE this will stop playback, and I think we should remove this and let the developer decide how to handle stalled instances + this._cleanUp(); // OJR this will stop playback, we could remove this and let the developer decide how to handle stalled instances this._sendEvent("failed"); }; p._handleSoundReady = function (event) { - if (window.createjs == null) { - return; - } - - // OJR would like a cleaner way to do this in _init, discuss with LM - this._duration = this.tag.duration * 1000; // need this for setPosition on stopped sounds - this.playState = createjs.Sound.PLAY_SUCCEEDED; this.paused = this._paused = false; this.tag.removeEventListener(createjs.HTMLAudioPlugin._AUDIO_READY, this._readyHandler, false); + this.tag.removeEventListener(createjs.HTMLAudioPlugin._AUDIO_SEEKED, this.loopHandler, false); if (this._offset >= this.getDuration()) { - this.playFailed(); // OJR: throw error? + this.playFailed(); return; - } else if (this._offset > 0) { - this.tag.currentTime = this._offset * 0.001; } - if (this._remainingLoops == -1) { - this.tag.loop = true; - } - if(this._remainingLoops != 0) { - this.tag.addEventListener(createjs.HTMLAudioPlugin._AUDIO_SEEKED, this.loopHandler, false); - this.tag.loop = true; + this.tag.currentTime = (this._startTime + this._offset) * 0.001; + + if (this._audioSpriteStopTime) { + this.tag.removeEventListener(createjs.HTMLAudioPlugin._AUDIO_ENDED, this._endedHandler, false); + this.tag.addEventListener(createjs.HTMLAudioPlugin._TIME_UPDATE, this.__audioSpriteEndHandler, false); + } else { + if(this._remainingLoops != 0) { + this.tag.addEventListener(createjs.HTMLAudioPlugin._AUDIO_SEEKED, this.loopHandler, false); + this.tag.loop = true; + } } + this.tag.play(); }; p.pause = function () { if (!this._paused && this.playState == createjs.Sound.PLAY_SUCCEEDED && this.tag != null) { this.paused = this._paused = true; - // Note: when paused by user, we hold a reference to our tag. We do not release it until stopped. this.tag.pause(); - clearTimeout(this._delayTimeoutId); - return true; } return false; }; p.resume = function () { - if (!this._paused || this.tag == null) { - return false; - } + if (!this._paused || this.tag == null) {return false;} this.paused = this._paused = false; this.tag.play(); return true; @@ -4761,7 +4698,6 @@ this.createjs = this.createjs || {}; p.setMasterVolume = function (value) { this._updateVolume(); - return true; }; p.setVolume = function (value) { @@ -4772,12 +4708,7 @@ this.createjs = this.createjs || {}; p._updateVolume = function () { if (this.tag != null) { var newVolume = (this._muted || createjs.Sound._masterMute) ? 0 : this._volume * createjs.Sound._masterVolume; - if (newVolume != this.tag.volume) { - this.tag.volume = newVolume; - } - return true; - } else { - return false; + if (newVolume != this.tag.volume) {this.tag.volume = newVolume;} } }; @@ -4787,14 +4718,10 @@ this.createjs = this.createjs || {}; p.setMasterMute = function (isMuted) { this._updateVolume(); - return true; }; p.setMute = function (isMuted) { - if (isMuted == null || isMuted == undefined) { - return false; - } - + if (isMuted == null) {return false;} this._muted = isMuted; this._updateVolume(); return true; @@ -4804,7 +4731,7 @@ this.createjs = this.createjs || {}; return this._muted; }; - // Can not set pan in HTML + // Can not set pan in HTML audio p.setPan = function (value) { return false; }; @@ -4814,10 +4741,8 @@ this.createjs = this.createjs || {}; }; p.getPosition = function () { - if (this.tag == null) { - return this._offset; - } - return this.tag.currentTime * 1000; + if (this.tag == null) {return this._offset;} + return (this.tag.currentTime * 1000) - this._startTime; }; p.setPosition = function (value) { @@ -4825,37 +4750,55 @@ this.createjs = this.createjs || {}; this._offset = value } else { this.tag.removeEventListener(createjs.HTMLAudioPlugin._AUDIO_SEEKED, this.loopHandler, false); + this.tag.addEventListener(createjs.HTMLAudioPlugin._AUDIO_SEEKED, this._handleSetPositionSeek, false); try { + value = value + this._startTime; this.tag.currentTime = value * 0.001; } catch (error) { // Out of range + this._handleSetPositionSeek(null); return false; } - this.tag.addEventListener(createjs.HTMLAudioPlugin._AUDIO_SEEKED, this.loopHandler, false); } return true; }; - p.getDuration = function () { // NOTE this will always return 0 until sound has been played. + p._handleSetPositionSeek = function(event) { + if (this.tag == null) { return; } + this.tag.removeEventListener(createjs.HTMLAudioPlugin._AUDIO_SEEKED, this._handleSetPositionSeek, false); + this.tag.addEventListener(createjs.HTMLAudioPlugin._AUDIO_SEEKED, this.loopHandler, false); + }; + + p.getDuration = function () { // NOTE this will always return 0 until sound has been played unless it is set return this._duration; }; p._handleSoundComplete = function (event) { this._offset = 0; - - if (window.createjs == null) { - return; - } this.playState = createjs.Sound.PLAY_FINISHED; this._cleanUp(); this._sendEvent("complete"); }; - // handles looping functionality + // NOTE because of the inaccuracies in the timeupdate event (15 - 250ms) and in setting the tag to the desired timed + // (up to 300ms), it is strongly recommended not to loop audio sprites with HTML Audio if smooth looping is desired + p._handleAudioSpriteLoop = function (event) { + if(this.tag.currentTime <= this._audioSpriteStopTime) {return;} + this.tag.pause(); + if(this._remainingLoops == 0) { + this._handleSoundComplete(null); + } else { + this._offset = 0; + this._remainingLoops--; + this.tag.currentTime = this._startTime * 0.001; + if(!this._paused) {this.tag.play();} + this._sendEvent("loop"); + } + }; + // NOTE with this approach audio will loop as reliably as the browser allows // but we could end up sending the loop event after next loop playback begins p.handleSoundLoop = function (event) { this._offset = 0; - this._remainingLoops--; if(this._remainingLoops == 0) { this.tag.loop = false; @@ -4865,9 +4808,6 @@ this.createjs = this.createjs || {}; }; p.playFailed = function () { - if (window.createjs == null) { - return; - } this.playState = createjs.Sound.PLAY_FAILED; this._cleanUp(); this._sendEvent("failed"); @@ -4902,6 +4842,7 @@ this.createjs = this.createjs || {}; }; var p = Loader.prototype; + p.constructor = Loader; /** * The source to be loaded. @@ -4946,12 +4887,12 @@ this.createjs = this.createjs || {}; this.loadedHandler = createjs.proxy(this.sendLoadedEvent, this); // we need this bind to be able to remove event listeners this.tag.addEventListener && this.tag.addEventListener("canplaythrough", this.loadedHandler); if(this.tag.onreadystatechange == null) { - this.tag.onreadystatechange = createjs.proxy(this.sendLoadedEvent, this); // OJR not 100% sure we need this, just copied from PreloadJS + this.tag.onreadystatechange = createjs.proxy(this.sendLoadedEvent, this); } else { var f = this.tag.onreadystatechange; this.tag.onreadystatechange = function() { f(); - this.tag.onreadystatechange = createjs.proxy(this.sendLoadedEvent, this); // OJR not 100% sure we need this, just copied from PreloadJS + this.tag.onreadystatechange = createjs.proxy(this.sendLoadedEvent, this); } } @@ -4994,6 +4935,7 @@ this.createjs = this.createjs || {}; this.tag.removeEventListener && this.tag.removeEventListener("canplaythrough", this.loadedHandler); // cleanup and so we don't send the event more than once this.tag.onreadystatechange = null; // cleanup and so we don't send the event more than once createjs.Sound._sendFileLoadEvent(this.src); // fire event or callback on Sound + }; // used for debugging @@ -5056,9 +4998,7 @@ this.createjs = this.createjs || {}; */ s.remove = function (src) { var channel = s.tags[src]; - if (channel == null) { - return false; - } + if (channel == null) {return false;} channel.removeAll(); delete(s.tags[src]); return true; @@ -5085,9 +5025,7 @@ this.createjs = this.createjs || {}; */ s.getInstance = function (src) { var channel = s.tags[src]; - if (channel == null) { - return null; - } + if (channel == null) {return null;} return channel.get(); }; @@ -5101,30 +5039,24 @@ this.createjs = this.createjs || {}; */ s.setInstance = function (src, tag) { var channel = s.tags[src]; - if (channel == null) { - return null; - } + if (channel == null) {return null;} return channel.set(tag); }; /** - * A function to check if src has changed in the loaded audio tag. - * This is required because PreloadJS appends a basePath to the src before loading. - * Note this is currently only called when a change is detected - * #method checkSrc - * @param src the unaltered src that is used to store the channel. - * @static - * @protected + * Gets the duration of the src audio in milliseconds + * #method getDuration + * @param {String} src The source file used by the audio tag. + * @return {Number} Duration of src in milliseconds */ - s.checkSrc = function (src) { + s.getDuration= function (src) { var channel = s.tags[src]; - if (channel == null) { - return null; - } - channel.checkSrcChange(); + if (channel == null) {return 0;} + return channel.getDuration(); }; var p = TagPool.prototype; + p.constructor = TagPool; /** * The source of the tag pool. @@ -5161,6 +5093,15 @@ this.createjs = this.createjs || {}; */ p.tags = null; + /** + * The duration property of all audio tags, converted to milliseconds, which originally is only available on the + * last tag in the tags array because that is the one that is loaded. + * #property + * @type {Number} + * @protected + */ + p.duration = 0; + // constructor p._init = function (src) { this.src = src; @@ -5183,8 +5124,12 @@ this.createjs = this.createjs || {}; * #method removeAll */ p.removeAll = function () { - // This may not be neccessary + var tag; while(this.length--) { + tag = this.tags[this.length]; + if(tag.parentNode) { + tag.parentNode.removeChild(tag); + } delete(this.tags[this.length]); // NOTE that the audio playback is already stopped by this point } this.src = null; @@ -5197,14 +5142,10 @@ this.createjs = this.createjs || {}; * @return {HTMLAudioElement} An HTML audio tag. */ p.get = function () { - if (this.tags.length == 0) { - return null; - } + if (this.tags.length == 0) {return null;} this.available = this.tags.length; var tag = this.tags.pop(); - if (tag.parentNode == null) { - document.body.appendChild(tag); - } + if (tag.parentNode == null) {document.body.appendChild(tag);} return tag; }; @@ -5215,26 +5156,19 @@ this.createjs = this.createjs || {}; */ p.set = function (tag) { var index = createjs.indexOf(this.tags, tag); - if (index == -1) { - this.tags.push(tag); - } + if (index == -1) {this.tags.push(tag);} this.available = this.tags.length; }; /** - * Make sure the src of all other tags is correct after load. - * This is needed because PreloadJS appends a basePath to src before loading. - * #method checkSrcChange + * Gets the duration for the src audio and on first call stores it to this.duration + * #method getDuration + * @return {Number} Duration of the src in milliseconds */ - p.checkSrcChange = function () { - // the last tag always has the latest src after loading - //var i = this.length-1; // this breaks in Firefox because it is not correctly removing an event listener - var i = this.tags.length - 1; - if(i == -1) return; // CodeCombat addition; sometimes errors in IE without this... - var newSrc = this.tags[i].src; - while(i--) { - this.tags[i].src = newSrc; - } + p.getDuration = function () { + // this will work because this will be only be run the first time a sound instance is created and before any tags are taken from the pool + if (!this.duration) {this.duration = this.tags[this.tags.length - 1].duration * 1000;} + return this.duration; }; p.toString = function () { @@ -5243,4 +5177,4 @@ this.createjs = this.createjs || {}; createjs.HTMLAudioPlugin.TagPool = TagPool; -}()); \ No newline at end of file +}()); diff --git a/vendor/scripts/tweenjs-NEXT.combined.js b/vendor/scripts/tweenjs-NEXT.combined.js index 179548b27..979542821 100644 --- a/vendor/scripts/tweenjs-NEXT.combined.js +++ b/vendor/scripts/tweenjs-NEXT.combined.js @@ -46,7 +46,7 @@ this.createjs = this.createjs||{}; /** * Contains properties and methods shared by all events for use with * {{#crossLink "EventDispatcher"}}{{/crossLink}}. - * + * * Note that Event objects are often reused, so you should never * rely on an event object's state outside of the call stack it was received in. * @class Event @@ -59,6 +59,7 @@ var Event = function(type, bubbles, cancelable) { this.initialize(type, bubbles, cancelable); }; var p = Event.prototype; +Event.prototype.constructor = Event; // events: @@ -162,7 +163,7 @@ var p = Event.prototype; * @readonly */ p.immediatePropagationStopped = false; - + /** * Indicates if {{#crossLink "Event/remove"}}{{/crossLink}} has been called on this event. * @property removed @@ -217,21 +218,21 @@ var p = Event.prototype; p.stopImmediatePropagation = function() { this.immediatePropagationStopped = this.propagationStopped = true; }; - + /** * Causes the active listener to be removed via removeEventListener(); - * + * * myBtn.addEventListener("click", function(evt) { * // do stuff... * evt.remove(); // removes this listener. * }); - * + * * @method remove **/ p.remove = function() { this.removed = true; }; - + /** * Returns a clone of the Event instance. * @method clone @@ -343,6 +344,7 @@ var EventDispatcher = function() { /* this.initialize(); */ // not needed. }; var p = EventDispatcher.prototype; +EventDispatcher.prototype.constructor = EventDispatcher; /** @@ -547,19 +549,19 @@ var p = EventDispatcher.prototype; * @param {Object | String | Event} eventObj An object with a "type" property, or a string type. * While a generic object will work, it is recommended to use a CreateJS Event instance. If a string is used, * dispatchEvent will construct an Event instance with the specified type. - * @param {Object} [target] The object to use as the target property of the event object. This will default to the - * dispatching object. This parameter is deprecated and will be removed. * @return {Boolean} Returns the value of eventObj.defaultPrevented. **/ - p.dispatchEvent = function(eventObj, target) { + p.dispatchEvent = function(eventObj) { if (typeof eventObj == "string") { // won't bubble, so skip everything if there's no listeners: var listeners = this._listeners; if (!listeners || !listeners[eventObj]) { return false; } eventObj = new createjs.Event(eventObj); + } else if (eventObj.target && eventObj.clone) { + // redispatching an active event object, so clone it: + eventObj = eventObj.clone(); } - // TODO: deprecated. Target param is deprecated, only use case is MouseEvent/mousemove, remove. - eventObj.target = target||this; + try { eventObj.target = this; } catch (e) {} // try/catch allows redispatching of native events if (!eventObj.bubbles || !this.parent) { this._dispatchEvent(eventObj, 2); @@ -631,8 +633,8 @@ var p = EventDispatcher.prototype; if (eventObj && listeners) { var arr = listeners[eventObj.type]; if (!arr||!(l=arr.length)) { return; } - eventObj.currentTarget = this; - eventObj.eventPhase = eventPhase; + try { eventObj.currentTarget = this; } catch (e) {} + try { eventObj.eventPhase = eventPhase; } catch (e) {} eventObj.removed = false; arr = arr.slice(); // to avoid issues with items being removed or added during the dispatch for (var i=0; iTicker.getPaused()
    ) and + * should not be instantiated. + * + *

    Example

    + * + * createjs.Ticker.addEventListener("tick", handleTick); + * function handleTick(event) { + * // Actions carried out each frame + * if (!event.paused) { + * // Actions carried out when the Ticker is not paused. + * } + * } + * + * To update a stage every tick, the {{#crossLink "Stage"}}{{/crossLink}} instance can also be used as a listener, as + * it will automatically update when it receives a tick event: + * + * createjs.Ticker.addEventListener("tick", stage); + * + * @class Ticker + * @uses EventDispatcher + * @static + **/ + function Ticker() { + throw "Ticker cannot be instantiated."; + } + + +// constants: + /** + * In this mode, Ticker uses the requestAnimationFrame API, but attempts to synch the ticks to target framerate. It + * uses a simple heuristic that compares the time of the RAF return to the target time for the current frame and + * dispatches the tick when the time is within a certain threshold. + * + * This mode has a higher variance for time between frames than TIMEOUT, but does not require that content be time + * based as with RAF while gaining the benefits of that API (screen synch, background throttling). + * + * Variance is usually lowest for framerates that are a divisor of the RAF frequency. This is usually 60, so + * framerates of 10, 12, 15, 20, and 30 work well. + * + * Falls back on TIMEOUT if the requestAnimationFrame API is not supported. + * @property RAF_SYNCHED + * @static + * @type {String} + * @default "synched" + * @readonly + **/ + Ticker.RAF_SYNCHED = "synched"; + + /** + * In this mode, Ticker passes through the requestAnimationFrame heartbeat, ignoring the target framerate completely. + * Because requestAnimationFrame frequency is not deterministic, any content using this mode should be time based. + * You can leverage {{#crossLink "Ticker/getTime"}}{{/crossLink}} and the tick event object's "delta" properties + * to make this easier. + * + * Falls back on TIMEOUT if the requestAnimationFrame API is not supported. + * @property RAF + * @static + * @type {String} + * @default "raf" + * @readonly + **/ + Ticker.RAF = "raf"; + + /** + * In this mode, Ticker uses the setTimeout API. This provides predictable, adaptive frame timing, but does not + * provide the benefits of requestAnimationFrame (screen synch, background throttling). + * @property TIMEOUT + * @static + * @type {String} + * @default "timer" + * @readonly + **/ + Ticker.TIMEOUT = "timeout"; + + +// static events: + /** + * Dispatched each tick. The event will be dispatched to each listener even when the Ticker has been paused using + * {{#crossLink "Ticker/setPaused"}}{{/crossLink}}. + * + *

    Example

    + * + * createjs.Ticker.addEventListener("tick", handleTick); + * function handleTick(event) { + * console.log("Paused:", event.paused, event.delta); + * } + * + * @event tick + * @param {Object} target The object that dispatched the event. + * @param {String} type The event type. + * @param {Boolean} paused Indicates whether the ticker is currently paused. + * @param {Number} delta The time elapsed in ms since the last tick. + * @param {Number} time The total time in ms since Ticker was initialized. + * @param {Number} runTime The total time in ms that Ticker was not paused since it was initialized. For example, + * you could determine the amount of time that the Ticker has been paused since initialization with time-runTime. + * @since 0.6.0 + */ + + +// public static properties: + /** + * Deprecated in favour of {{#crossLink "Ticker/timingMode"}}{{/crossLink}}, and will be removed in a future version. If true, timingMode will + * use {{#crossLink "Ticker/RAF_SYNCHED"}}{{/crossLink}} by default. + * @deprecated Deprecated in favour of {{#crossLink "Ticker/timingMode"}}{{/crossLink}}. + * @property useRAF + * @static + * @type {Boolean} + * @default false + **/ + Ticker.useRAF = false; + + /** + * Specifies the timing api (setTimeout or requestAnimationFrame) and mode to use. See + * {{#crossLink "Ticker/TIMEOUT"}}{{/crossLink}}, {{#crossLink "Ticker/RAF"}}{{/crossLink}}, and + * {{#crossLink "Ticker/RAF_SYNCHED"}}{{/crossLink}} for mode details. + * @property timingMode + * @static + * @type {String} + * @default Ticker.TIMEOUT + **/ + Ticker.timingMode = null; + + /** + * Specifies a maximum value for the delta property in the tick event object. This is useful when building time + * based animations and systems to prevent issues caused by large time gaps caused by background tabs, system sleep, + * alert dialogs, or other blocking routines. Double the expected frame duration is often an effective value + * (ex. maxDelta=50 when running at 40fps). + * + * This does not impact any other values (ex. time, runTime, etc), so you may experience issues if you enable maxDelta + * when using both delta and other values. + * + * If 0, there is no maximum. + * @property maxDelta + * @static + * @type {number} + * @default 0 + */ + Ticker.maxDelta = 0; + + +// mix-ins: + // EventDispatcher methods: + Ticker.removeEventListener = null; + Ticker.removeAllEventListeners = null; + Ticker.dispatchEvent = null; + Ticker.hasEventListener = null; + Ticker._listeners = null; + createjs.EventDispatcher.initialize(Ticker); // inject EventDispatcher methods. + Ticker._addEventListener = Ticker.addEventListener; + Ticker.addEventListener = function() { + !Ticker._inited&&Ticker.init(); + return Ticker._addEventListener.apply(Ticker, arguments); + }; + + +// private static properties: + /** + * @property _paused + * @type {Boolean} + * @protected + **/ + Ticker._paused = false; + + /** + * @property _inited + * @type {Boolean} + * @protected + **/ + Ticker._inited = false; + + /** + * @property _startTime + * @type {Number} + * @protected + **/ + Ticker._startTime = 0; + + /** + * @property _pausedTime + * @type {Number} + * @protected + **/ + Ticker._pausedTime=0; + + /** + * The number of ticks that have passed + * @property _ticks + * @type {Number} + * @protected + **/ + Ticker._ticks = 0; + + /** + * The number of ticks that have passed while Ticker has been paused + * @property _pausedTicks + * @type {Number} + * @protected + **/ + Ticker._pausedTicks = 0; + + /** + * @property _interval + * @type {Number} + * @protected + **/ + Ticker._interval = 50; + + /** + * @property _lastTime + * @type {Number} + * @protected + **/ + Ticker._lastTime = 0; + + /** + * @property _times + * @type {Array} + * @protected + **/ + Ticker._times = null; + + /** + * @property _tickTimes + * @type {Array} + * @protected + **/ + Ticker._tickTimes = null; + + /** + * Stores the timeout or requestAnimationFrame id. + * @property _timerId + * @type {Number} + * @protected + **/ + Ticker._timerId = null; + + /** + * True if currently using requestAnimationFrame, false if using setTimeout. + * @property _raf + * @type {Boolean} + * @protected + **/ + Ticker._raf = true; + + +// public static methods: + /** + * Starts the tick. This is called automatically when the first listener is added. + * @method init + * @static + **/ + Ticker.init = function() { + if (Ticker._inited) { return; } + Ticker._inited = true; + Ticker._times = []; + Ticker._tickTimes = []; + Ticker._startTime = Ticker._getTime(); + Ticker._times.push(Ticker._lastTime = 0); + Ticker.setInterval(Ticker._interval); + }; + + /** + * Stops the Ticker and removes all listeners. Use init() to restart the Ticker. + * @method reset + * @static + **/ + Ticker.reset = function() { + if (Ticker._raf) { + var f = window.cancelAnimationFrame || window.webkitCancelAnimationFrame || window.mozCancelAnimationFrame || window.oCancelAnimationFrame || window.msCancelAnimationFrame; + f&&f(Ticker._timerId); + } else { + clearTimeout(Ticker._timerId); + } + Ticker.removeAllEventListeners("tick"); + Ticker._timerId = null; + Ticker._inited = false; + }; + + /** + * Sets the target time (in milliseconds) between ticks. Default is 50 (20 FPS). + * + * Note actual time between ticks may be more than requested depending on CPU load. + * @method setInterval + * @static + * @param {Number} interval Time in milliseconds between ticks. Default value is 50. + **/ + Ticker.setInterval = function(interval) { + Ticker._interval = interval; + if (!Ticker._inited) { return; } + Ticker._setupTick(); + }; + + /** + * Returns the current target time between ticks, as set with {{#crossLink "Ticker/setInterval"}}{{/crossLink}}. + * @method getInterval + * @static + * @return {Number} The current target interval in milliseconds between tick events. + **/ + Ticker.getInterval = function() { + return Ticker._interval; + }; + + /** + * Sets the target frame rate in frames per second (FPS). For example, with an interval of 40, getFPS() + * will return 25 (1000ms per second divided by 40 ms per tick = 25fps). + * @method setFPS + * @static + * @param {Number} value Target number of ticks broadcast per second. + **/ + Ticker.setFPS = function(value) { + Ticker.setInterval(1000/value); + }; + + /** + * Returns the target frame rate in frames per second (FPS). For example, with an interval of 40, getFPS() + * will return 25 (1000ms per second divided by 40 ms per tick = 25fps). + * @method getFPS + * @static + * @return {Number} The current target number of frames / ticks broadcast per second. + **/ + Ticker.getFPS = function() { + return 1000/Ticker._interval; + }; + + /** + * Returns the average time spent within a tick. This can vary significantly from the value provided by getMeasuredFPS + * because it only measures the time spent within the tick execution stack. + * + * Example 1: With a target FPS of 20, getMeasuredFPS() returns 20fps, which indicates an average of 50ms between + * the end of one tick and the end of the next. However, getMeasuredTickTime() returns 15ms. This indicates that + * there may be up to 35ms of "idle" time between the end of one tick and the start of the next. + * + * Example 2: With a target FPS of 30, getFPS() returns 10fps, which indicates an average of 100ms between the end of + * one tick and the end of the next. However, getMeasuredTickTime() returns 20ms. This would indicate that something + * other than the tick is using ~80ms (another script, DOM rendering, etc). + * @method getMeasuredTickTime + * @static + * @param {Number} [ticks] The number of previous ticks over which to measure the average time spent in a tick. + * Defaults to the number of ticks per second. To get only the last tick's time, pass in 1. + * @return {Number} The average time spent in a tick in milliseconds. + **/ + Ticker.getMeasuredTickTime = function(ticks) { + var ttl=0, times=Ticker._tickTimes; + if (!times || times.length < 1) { return -1; } + + // by default, calculate average for the past ~1 second: + ticks = Math.min(times.length, ticks||(Ticker.getFPS()|0)); + for (var i=0; itick
    event. When the ticker is paused, all + * listeners will still receive a tick event, but the paused property will be false. + * + * Note that in EaselJS v0.5.0 and earlier, "pauseable" listeners would not receive the tick + * callback when Ticker was paused. This is no longer the case. + * + *

    Example

    + * + * createjs.Ticker.addEventListener("tick", handleTick); + * createjs.Ticker.setPaused(true); + * function handleTick(event) { + * console.log("Paused:", event.paused, createjs.Ticker.getPaused()); + * } + * + * @method setPaused + * @static + * @param {Boolean} value Indicates whether to pause (true) or unpause (false) Ticker. + **/ + Ticker.setPaused = function(value) { + Ticker._paused = value; + }; + + /** + * Returns a boolean indicating whether Ticker is currently paused, as set with {{#crossLink "Ticker/setPaused"}}{{/crossLink}}. + * When the ticker is paused, all listeners will still receive a tick event, but this value will be false. + * + * Note that in EaselJS v0.5.0 and earlier, "pauseable" listeners would not receive the tick + * callback when Ticker was paused. This is no longer the case. + * + *

    Example

    + * + * createjs.Ticker.addEventListener("tick", handleTick); + * createjs.Ticker.setPaused(true); + * function handleTick(event) { + * console.log("Paused:", createjs.Ticker.getPaused()); + * } + * + * @method getPaused + * @static + * @return {Boolean} Whether the Ticker is currently paused. + **/ + Ticker.getPaused = function() { + return Ticker._paused; + }; + + /** + * Returns the number of milliseconds that have elapsed since Ticker was initialized via {{#crossLink "Ticker/init"}}. + * Returns -1 if Ticker has not been initialized. For example, you could use + * this in a time synchronized animation to determine the exact amount of time that has elapsed. + * @method getTime + * @static + * @param {Boolean} [runTime=false] If true only time elapsed while Ticker was not paused will be returned. + * If false, the value returned will be total time elapsed since the first tick event listener was added. + * @return {Number} Number of milliseconds that have elapsed since Ticker was initialized or -1. + **/ + Ticker.getTime = function(runTime) { + return Ticker._startTime ? Ticker._getTime() - Ticker._startTime - (runTime ? Ticker._pausedTime : 0) : -1; + }; + + /** + * Similar to getTime(), but returns the time included with the current (or most recent) tick event object. + * @method getEventTime + * @param runTime {Boolean} [runTime=false] If true, the runTime property will be returned instead of time. + * @returns {number} The time or runTime property from the most recent tick event or -1. + */ + Ticker.getEventTime = function(runTime) { + return Ticker._startTime ? (Ticker._lastTime || Ticker._startTime) - (runTime ? Ticker._pausedTime : 0) : -1; + }; + + /** + * Returns the number of ticks that have been broadcast by Ticker. + * @method getTicks + * @static + * @param {Boolean} pauseable Indicates whether to include ticks that would have been broadcast + * while Ticker was paused. If true only tick events broadcast while Ticker is not paused will be returned. + * If false, tick events that would have been broadcast while Ticker was paused will be included in the return + * value. The default value is false. + * @return {Number} of ticks that have been broadcast. + **/ + Ticker.getTicks = function(pauseable) { + return Ticker._ticks - (pauseable ?Ticker._pausedTicks : 0); + }; + + +// private static methods: + /** + * @method _handleSynch + * @static + * @protected + **/ + Ticker._handleSynch = function() { + Ticker._timerId = null; + Ticker._setupTick(); + + // run if enough time has elapsed, with a little bit of flexibility to be early: + if (Ticker._getTime() - Ticker._lastTime >= (Ticker._interval-1)*0.97) { + Ticker._tick(); + } + }; + + /** + * @method _handleRAF + * @static + * @protected + **/ + Ticker._handleRAF = function() { + Ticker._timerId = null; + Ticker._setupTick(); + Ticker._tick(); + }; + + /** + * @method _handleTimeout + * @static + * @protected + **/ + Ticker._handleTimeout = function() { + Ticker._timerId = null; + Ticker._setupTick(); + Ticker._tick(); + }; + + /** + * @method _setupTick + * @static + * @protected + **/ + Ticker._setupTick = function() { + if (Ticker._timerId != null) { return; } // avoid duplicates + + var mode = Ticker.timingMode||(Ticker.useRAF&&Ticker.RAF_SYNCHED); + if (mode == Ticker.RAF_SYNCHED || mode == Ticker.RAF) { + var f = window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame || window.oRequestAnimationFrame || window.msRequestAnimationFrame; + if (f) { + Ticker._timerId = f(mode == Ticker.RAF ? Ticker._handleRAF : Ticker._handleSynch); + Ticker._raf = true; + return; + } + } + Ticker._raf = false; + Ticker._timerId = setTimeout(Ticker._handleTimeout, Ticker._interval); + }; + + /** + * @method _tick + * @static + * @protected + **/ + Ticker._tick = function() { + var time = Ticker._getTime(); + var adjTime = time-Ticker._startTime; + var elapsedTime = time-Ticker._lastTime; + var paused = Ticker._paused; + + Ticker._ticks++; + if (paused) { + Ticker._pausedTicks++; + Ticker._pausedTime += elapsedTime; + } + Ticker._lastTime = time; + + if (Ticker.hasEventListener("tick")) { + var event = new createjs.Event("tick"); + var maxDelta = Ticker.maxDelta; + event.delta = (maxDelta && elapsedTime > maxDelta) ? maxDelta : elapsedTime; + event.paused = paused; + event.time = adjTime; + event.runTime = adjTime-Ticker._pausedTime; + Ticker.dispatchEvent(event); + } + + Ticker._tickTimes.unshift(Ticker._getTime()-time); + while (Ticker._tickTimes.length > 100) { Ticker._tickTimes.pop(); } + + Ticker._times.unshift(adjTime); + while (Ticker._times.length > 100) { Ticker._times.pop(); } + }; + + /** + * @method _getTime + * @static + * @protected + **/ + var now = window.performance && (performance.now || performance.mozNow || performance.msNow || performance.oNow || performance.webkitNow); + Ticker._getTime = function() { + return (now&&now.call(performance))||(new Date().getTime()); + }; + + + createjs.Ticker = Ticker; +}()); +/* * Tween * Visit http://createjs.com/ for documentation, updates and examples. * @@ -710,11 +1319,6 @@ createjs.EventDispatcher = EventDispatcher; * //Tween complete * } * - *

    Required Support

    - * Tweenjs requires a ticker function, which is included in EaselJS. - * If you are not using EaselJS, you must build your own ticker function that calls {{#crossLink "Tween/tick"}}{{/crossLink}} - * on the tweens. - * *

    Browser Support

    * TweenJS will work in all browsers. * @@ -764,7 +1368,7 @@ this.createjs = this.createjs||{}; * All properties default to false. Supported props are:
      *
    • loop: sets the loop property on this tween.
    • *
    • useTicks: uses ticks for all durations instead of milliseconds.
    • - *
    • ignoreGlobalPause: sets the ignoreGlobalPause property on this tween.
    • + *
    • ignoreGlobalPause: sets the {{#crossLink "Tween/ignoreGlobalPause:property"}}{{/crossLink}} property on this tween.
    • *
    • override: if true, `Tween.removeTweens(target)` will be called to remove any other tweens with the same target. *
    • paused: indicates whether to start the tween paused.
    • *
    • position: indicates the initial position for this tween.
    • @@ -779,6 +1383,7 @@ var Tween = function(target, props, pluginData) { this.initialize(target, props, pluginData); }; var p = Tween.prototype = new createjs.EventDispatcher(); +Tween.prototype.constructor = Tween; // static interface: /** @@ -843,7 +1448,7 @@ var p = Tween.prototype = new createjs.EventDispatcher(); * All properties default to false. Supported props are:
        *
      • loop: sets the loop property on this tween.
      • *
      • useTicks: uses ticks for all durations instead of milliseconds.
      • - *
      • ignoreGlobalPause: sets the ignoreGlobalPause property on this tween.
      • + *
      • ignoreGlobalPause: sets the {{#crossLink "Tween/ignoreGlobalPause:property"}}{{/crossLink}} property on this tween.
      • *
      • override: if true, Tween.removeTweens(target) will be called to remove any other tweens with the same target. *
      • paused: indicates whether to start the tween paused.
      • *
      • position: indicates the initial position for this tween.
      • @@ -863,15 +1468,13 @@ var p = Tween.prototype = new createjs.EventDispatcher(); }; /** - * Advances all tweens. This typically uses the Ticker class (available in the EaselJS library), but you can call it + * Advances all tweens. This typically uses the {{#crossLink "Ticker"}}{{/crossLink}} class, but you can call it * manually if you prefer to use your own "heartbeat" implementation. - * - * Note: Currently, EaselJS must be included before TweenJS to ensure Ticker exists during initialization. * @method tick * @param {Number} delta The change in time in milliseconds since the last tick. Required unless all tweens have * useTicks set to true. - * @param {Boolean} paused Indicates whether a global pause is in effect. Tweens with ignoreGlobalPause - * will ignore this, but all others will pause if this is true. + * @param {Boolean} paused Indicates whether a global pause is in effect. Tweens with {{#crossLink "Tween/ignoreGlobalPause:property"}}{{/crossLink}} + * will ignore this, but all others will pause if this is `true`. * @static */ Tween.tick = function(delta, paused) { @@ -887,7 +1490,8 @@ var p = Tween.prototype = new createjs.EventDispatcher(); * Handle events that result from Tween being used as an event handler. This is included to allow Tween to handle * tick events from createjs.Ticker. No other events are handled in Tween. * @method handleEvent - * @param {Object} event An event object passed in by the EventDispatcher. Will usually be of type "tick". + * @param {Object} event An event object passed in by the {{#crossLink "EventDispatcher"}}{{/crossLink}}. Will + * usually be of type "tick". * @private * @static * @since 0.4.2 @@ -909,9 +1513,10 @@ var p = Tween.prototype = new createjs.EventDispatcher(); if (!target.tweenjs_count) { return; } var tweens = Tween._tweens; for (var i=tweens.length-1; i>=0; i--) { - if (tweens[i]._target == target) { - tweens[i]._paused = true; - tweens.splice(i,1); + var tween = tweens[i]; + if (tween._target == target) { + tween._paused = true; + tweens.splice(i, 1); } } target.tweenjs_count = 0; @@ -927,7 +1532,7 @@ var p = Tween.prototype = new createjs.EventDispatcher(); var tweens = Tween._tweens; for (var i= 0, l=tweens.length; iTicker.setPaused(true) is called. - * See Tween.tick() for more info. Can be set via the props param. + * Causes this tween to continue playing when a global pause is active. For example, if TweenJS is using {{#crossLink "Ticker"}}{{/crossLink}}, + * then setting this to true (the default) will cause this tween to be paused when Ticker.setPaused(true) + * is called. See the Tween {{#crossLink "Tween/tick"}}{{/crossLink}} method for more info. Can be set via the props + * parameter. * @property ignoreGlobalPause * @type Boolean * @default false @@ -1360,7 +1966,8 @@ var p = Tween.prototype = new createjs.EventDispatcher(); /** * Advances this tween by the specified amount of time in milliseconds (or ticks if useTicks is true). - * This is normally called automatically by the Tween engine (via Tween.tick), but is exposed for advanced uses. + * This is normally called automatically by the Tween engine (via Tween.tick), but is exposed for + * advanced uses. * @method tick * @param {Number} delta The time to advance in milliseconds (or ticks if useTicks is true). */ @@ -1376,6 +1983,7 @@ var p = Tween.prototype = new createjs.EventDispatcher(); * @return {Tween} This tween instance (for chaining calls) */ p.setPaused = function(value) { + if (this._paused === !!value) { return this; } this._paused = !!value; Tween._register(this, !value); return this; @@ -1630,6 +2238,7 @@ var Timeline = function(tweens, labels, props) { this.initialize(tweens, labels, props); }; var p = Timeline.prototype = new createjs.EventDispatcher(); +Timeline.prototype.constructor = Timeline; // public properties: @@ -1701,7 +2310,7 @@ var p = Timeline.prototype = new createjs.EventDispatcher(); * @protected **/ p._labels = null; - + /** * @property _labelList * @type Array[Object] @@ -1828,7 +2437,7 @@ var p = Timeline.prototype = new createjs.EventDispatcher(); p.setLabels = function(o) { this._labels = o ? o : {}; }; - + /** * Returns a sorted list of the labels defined on this timeline. * @method getLabels @@ -1846,7 +2455,7 @@ var p = Timeline.prototype = new createjs.EventDispatcher(); } return list; }; - + /** * Returns the name of the label on or immediately before the current position. For example, given a timeline with * two labels, "first" on frame index 4, and "second" on frame 8, getCurrentLabel would return:
          @@ -1867,7 +2476,7 @@ var p = Timeline.prototype = new createjs.EventDispatcher(); } return null; }; - + /** * Unpauses this timeline and jumps to the specified position or label. * @method gotoAndPlay @@ -1953,7 +2562,7 @@ var p = Timeline.prototype = new createjs.EventDispatcher(); * @param {String|Number} positionOrLabel A numeric position value or label string. **/ p.resolve = function(positionOrLabel) { - var pos = parseFloat(positionOrLabel); + var pos = Number(positionOrLabel); if (isNaN(pos)) { pos = this._labels[positionOrLabel]; } return pos; }; @@ -2755,6 +3364,6 @@ this.createjs = this.createjs || {}; * @type String * @static **/ - s.buildDate = /*date*/"Thu, 12 Dec 2013 23:37:07 GMT"; // injected by build process + s.buildDate = /*date*/"Fri, 24 Oct 2014 16:09:53 GMT"; // injected by build process })();