/** * @author Richard Davey<EMAIL_ADDRESS>* @copyright 2015 Photon Storm Ltd. * @license {@link https://github.com/photonstorm/phaser/blob/master/license.txt|MIT License} */ /** * @classdesc * The ScaleManager object handles the the scaling, resizing, and alignment of the * Game size and the game Display canvas. * * The Game size is the logical size of the game; the Display canvas has size as an HTML element. * * The calculations of these are heavily influenced by the bounding Parent size which is the computed * dimensions of the Display canvas's Parent container/element - the _effective CSS rules of the * canvas's Parent element play an important role_ in the operation of the ScaleManager. * * The Display canvas - or Game size, depending {@link #scaleMode} - is updated to best utilize the Parent size. * When in Fullscreen mode or with {@link #parentIsWindow} the Parent size is that of the visual viewport (see {@link Phaser.ScaleManager#getParentBounds getParentBounds}). * * Parent and Display canvas containment guidelines: * * - Style the Parent element (of the game canvas) to control the Parent size and * thus the Display canvas's size and layout. * * - The Parent element's CSS styles should _effectively_ apply maximum (and minimum) bounding behavior. * * - The Parent element should _not_ apply a padding as this is not accounted for. * If a padding is required apply it to the Parent's parent or apply a margin to the Parent. * If you need to add a border, margin or any other CSS around your game container, then use a parent element and * apply the CSS to this instead, otherwise you'll be constantly resizing the shape of the game container. * * - The Display canvas layout CSS styles (i.e. margins, size) should not be altered/specified as * they may be updated by the ScaleManager. * * @description * Create a new ScaleManager object - this is done automatically by {@link Phaser.Game} * * The `width` and `height` constructor parameters can either be a number which represents pixels or a string that represents a percentage: e.g. `800` (for 800 pixels) or `"80%"` for 80%. * * @class * @param {Phaser.Game} game - A reference to the currently running game. * @param {number|string} width - The width of the game. See above. * @param {number|string} height - The height of the game. See above. */ Phaser.ScaleManager = function (game, width, height) { /** * A reference to the currently running game. * @property {Phaser.Game} game * @protected * @readonly */ this.game = game; /** * Provides access to some cross-device DOM functions. * @property {Phaser.DOM} dom * @protected * @readonly */ this.dom = Phaser.DOM; /** * _EXPERIMENTAL:_ A responsive grid on which you can align game objects. * @property {Phaser.FlexGrid} grid * @public */ this.grid = null; /** * Target width (in pixels) of the Display canvas. * @property {number} width * @readonly */ this.width = 0; /** * Target height (in pixels) of the Display canvas. * @property {number} height * @readonly */ this.height = 0; /** * Minimum width the canvas should be scaled to (in pixels). * Change with {@link #setMinMax}. * @property {?number} minWidth * @readonly * @protected */ this.minWidth = null; /** * Maximum width the canvas should be scaled to (in pixels). * If null it will scale to whatever width the browser can handle. * Change with {@link #setMinMax}. * @property {?number} maxWidth * @readonly * @protected */ this.maxWidth = null; /** * Minimum height the canvas should be scaled to (in pixels). * Change with {@link #setMinMax}. * @property {?number} minHeight * @readonly * @protected */ this.minHeight = null; /** * Maximum height the canvas should be scaled to (in pixels). * If null it will scale to whatever height the browser can handle. * Change with {@link #setMinMax}. * @property {?number} maxHeight * @readonly * @protected */ this.maxHeight = null; /** * The offset coordinates of the Display canvas from the top-left of the browser window. * The is used internally by Phaser.Pointer (for Input) and possibly other types. * @property {Phaser.Point} offset * @readonly * @protected */ this.offset = new Phaser.Point(); /** * If true, the game should only run in a landscape orientation. * Change with {@link #forceOrientation}. * @property {boolean} forceLandscape * @readonly * @default * @protected */ this.forceLandscape = false; /** * If true, the game should only run in a portrait * Change with {@link #forceOrientation}. * @property {boolean} forcePortrait * @readonly * @default * @protected */ this.forcePortrait = false; /** * True if {@link #forceLandscape} or {@link #forcePortrait} are set and do not agree with the browser orientation. * * This value is not updated immediately. * * @property {boolean} incorrectOrientation * @readonly * @protected */ this.incorrectOrientation = false; /** * See {@link #pageAlignHorizontally}. * @property {boolean} _pageAlignHorizontally * @private */ this._pageAlignHorizontally = false; /** * See {@link #pageAlignVertically}. * @property {boolean} _pageAlignVertically * @private */ this._pageAlignVertically = false; /** * This signal is dispatched when the orientation changes _or_ the validity of the current orientation changes. * * The signal is supplied with the following arguments: * - `scale` - the ScaleManager object * - `prevOrientation`, a string - The previous orientation as per {@link Phaser.ScaleManager#screenOrientation screenOrientation}. * - `wasIncorrect`, a boolean - True if the previous orientation was last determined to be incorrect. * * Access the current orientation and validity with `scale.screenOrientation` and `scale.incorrectOrientation`. * Thus the following tests can be done: * * // The orientation itself changed: * scale.screenOrientation !== prevOrientation * // The orientation just became incorrect: * scale.incorrectOrientation && !wasIncorrect * * It is possible that this signal is triggered after {@link #forceOrientation} so the orientation * correctness changes even if the orientation itself does not change. * * This is signaled from `preUpdate` (or `pauseUpdate`) _even when_ the game is paused. * * @property {Phaser.Signal} onOrientationChange * @public */ this.onOrientationChange = new Phaser.Signal(); /** * This signal is dispatched when the browser enters an incorrect orientation, as defined by {@link #forceOrientation}. * * This is signaled from `preUpdate` (or `pauseUpdate`) _even when_ the game is paused. * * @property {Phaser.Signal} enterIncorrectOrientation * @public */ this.enterIncorrectOrientation = new Phaser.Signal(); /** * This signal is dispatched when the browser leaves an incorrect orientation, as defined by {@link #forceOrientation}. * * This is signaled from `preUpdate` (or `pauseUpdate`) _even when_ the game is paused. * * @property {Phaser.Signal} leaveIncorrectOrientation * @public */ this.leaveIncorrectOrientation = new Phaser.Signal(); /** * If specified, this is the DOM element on which the Fullscreen API enter request will be invoked. * The target element must have the correct CSS styling and contain the Display canvas. * * The elements style will be modified (ie. the width and height might be set to 100%) * but it will not be added to, removed from, or repositioned within the DOM. * An attempt is made to restore relevant style changes when fullscreen mode is left. * * For pre-2.2.0 behavior set `game.scale.fullScreenTarget = game.canvas`. * * @property {?DOMElement} fullScreenTarget * @default */ this.fullScreenTarget = null; /** * The fullscreen target, as created by {@link #createFullScreenTarget}. * This is not set if {@link #fullScreenTarget} is used and is cleared when fullscreen mode ends. * @property {?DOMElement} _createdFullScreenTarget * @private */ this._createdFullScreenTarget = null; /** * This signal is dispatched when fullscreen mode is ready to be initialized but * before the fullscreen request. * * The signal is passed two arguments: `scale` (the ScaleManager), and an object in the form `{targetElement: DOMElement}`. * * The `targetElement` is the {@link #fullScreenTarget} element, * if such is assigned, or a new element created by {@link #createFullScreenTarget}. * * Custom CSS styling or resets can be applied to `targetElement` as required. * * If `targetElement` is _not_ the same element as {@link #fullScreenTarget}: * - After initialization the Display canvas is moved onto the `targetElement` for * the duration of the fullscreen mode, and restored to it's original DOM location when fullscreen is exited. * - The `targetElement` is moved/re-parented within the DOM and may have its CSS styles updated. * * The behavior of a pre-assigned target element is covered in {@link Phaser.ScaleManager#fullScreenTarget fullScreenTarget}. * * @property {Phaser.Signal} onFullScreenInit * @public */ this.onFullScreenInit = new Phaser.Signal(); /** * This signal is dispatched when the browser enters or leaves fullscreen mode, if supported. * * The signal is supplied with a single argument: `scale` (the ScaleManager). Use `scale.isFullScreen` to determine * if currently running in Fullscreen mode. * * @property {Phaser.Signal} onFullScreenChange * @public */ this.onFullScreenChange = new Phaser.Signal(); /** * This signal is dispatched when the browser fails to enter fullscreen mode; * or if the device does not support fullscreen mode and `startFullScreen` is invoked. * * The signal is supplied with a single argument: `scale` (the ScaleManager). * * @property {Phaser.Signal} onFullScreenError * @public */ this.onFullScreenError = new Phaser.Signal(); /** * The _last known_ orientation of the screen, as defined in the Window Screen Web API. * See {@link Phaser.DOM.getScreenOrientation} for possible values. * * @property {string} screenOrientation * @readonly * @public */ this.screenOrientation = this.dom.getScreenOrientation(); /** * The _current_ scale factor based on the game dimensions vs. the scaled dimensions. * @property {Phaser.Point} scaleFactor * @readonly */ this.scaleFactor = new Phaser.Point(1, 1); /** * The _current_ inversed scale factor. The displayed dimensions divided by the game dimensions. * @property {Phaser.Point} scaleFactorInversed * @readonly * @protected */ this.scaleFactorInversed = new Phaser.Point(1, 1); /** * The Display canvas is aligned by adjusting the margins; the last margins are stored here. * * @property {Bounds-like} margin * @readonly * @protected */ this.margin = {left: 0, top: 0, right: 0, bottom: 0, x: 0, y: 0}; /** * The bounds of the scaled game. The x/y will match the offset of the canvas element and the width/height the scaled width and height. * @property {Phaser.Rectangle} bounds * @readonly */ this.bounds = new Phaser.Rectangle(); /** * The aspect ratio of the scaled Display canvas. * @property {number} aspectRatio * @readonly */ this.aspectRatio = 0; /** * The aspect ratio of the original game dimensions. * @property {number} sourceAspectRatio * @readonly */ this.sourceAspectRatio = 0; /** * The native browser events from Fullscreen API changes. * @property {any} event * @readonly * @private */ this.event = null; /** * The edges on which to constrain the game Display/canvas in _addition_ to the restrictions of the parent container. * * The properties are strings and can be '', 'visual', 'layout', or 'layout-soft'. * - If 'visual', the edge will be constrained to the Window / displayed screen area * - If 'layout', the edge will be constrained to the CSS Layout bounds * - An invalid value is treated as 'visual' * * @member * @property {string} bottom * @property {string} right * @default */ this.windowConstraints = { right: 'layout', bottom: '' }; /** * Various compatibility settings. * A value of "(auto)" indicates the setting is configured based on device and runtime information. * * A {@link #refresh} may need to be performed after making changes. * * @protected * * @property {boolean} [supportsFullScreen=(auto)] - True only if fullscreen support will be used. (Changing to fullscreen still might not work.) * * @property {boolean} [orientationFallback=(auto)] - See {@link Phaser.DOM.getScreenOrientation}. * * @property {boolean} [noMargins=false] - If true then the Display canvas's margins will not be updated anymore: existing margins must be manually cleared. Disabling margins prevents automatic canvas alignment/centering, possibly in fullscreen. * * @property {?Phaser.Point} [scrollTo=(auto)] - If specified the window will be scrolled to this position on every refresh. * * @property {boolean} [forceMinimumDocumentHeight=false] - If enabled the document elements minimum height is explicitly set on updates. * The height set varies by device and may either be the height of the window or the viewport. * * @property {boolean} [canExpandParent=true] - If enabled then SHOW_ALL and USER_SCALE modes can try and expand the parent element. It may be necessary for the parent element to impose CSS width/height restrictions. * * @property {string} [clickTrampoline=(auto)] - On certain browsers (eg. IE) FullScreen events need to be triggered via 'click' events. * A value of 'when-not-mouse' uses a click trampoline when a pointer that is not the primary mouse is used. * Any other string value (including the empty string) prevents using click trampolines. * For more details on click trampolines see {@link Phaser.Pointer#addClickTrampoline}. */ this.compatibility = { supportsFullScreen: false, orientationFallback: null, noMargins: false, scrollTo: null, forceMinimumDocumentHeight: false, canExpandParent: true, clickTrampoline: '' }; /** * Scale mode to be used when not in fullscreen. * @property {number} _scaleMode * @private */ this._scaleMode = Phaser.ScaleManager.NO_SCALE; /* * Scale mode to be used in fullscreen. * @property {number} _fullScreenScaleMode * @private */ this._fullScreenScaleMode = Phaser.ScaleManager.NO_SCALE; /** * If the parent container of the Game canvas is the browser window itself (i.e. document.body), * rather than another div, this should set to `true`. * * The {@link #parentNode} property is generally ignored while this is in effect. * * @property {boolean} parentIsWindow */ this.parentIsWindow = false; /** * The _original_ DOM element for the parent of the Display canvas. * This may be different in fullscreen - see {@link #createFullScreenTarget}. * * This should only be changed after moving the Game canvas to a different DOM parent. * * @property {?DOMElement} parentNode */ this.parentNode = null; /** * The scale of the game in relation to its parent container. * @property {Phaser.Point} parentScaleFactor * @readonly */ this.parentScaleFactor = new Phaser.Point(1, 1); /** * The maximum time (in ms) between dimension update checks for the Canvas's parent element (or window). * Update checks normally happen quicker in response to other events. * * @property {integer} trackParentInterval * @default * @protected * @see {@link Phaser.ScaleManager#refresh refresh} */ this.trackParentInterval = 2000; /** * This signal is dispatched when the size of the Display canvas changes _or_ the size of the Game changes. * When invoked this is done _after_ the Canvas size/position have been updated. * * This signal is _only_ called when a change occurs and a reflow may be required. * For example, if the canvas does not change sizes because of CSS settings (such as min-width) * then this signal will _not_ be triggered. * * Use this to handle responsive game layout options. * * This is signaled from `preUpdate` (or `pauseUpdate`) _even when_ the game is paused. * * @property {Phaser.Signal} onSizeChange * @todo Formalize the arguments, if any, supplied to this signal. */ this.onSizeChange = new Phaser.Signal(); /** * The callback that will be called each the parent container resizes. * @property {function} onResize * @private */ this.onResize = null; /** * The context in which the {@link #onResize} callback will be called. * @property {object} onResizeContext * @private */ this.onResizeContext = null; /** * @property {integer} _pendingScaleMode - Used to retain the scale mode if set from config before Boot. * @private */ this._pendingScaleMode = null; /** * Information saved when fullscreen mode is started. * @property {?object} _fullScreenRestore * @private */ this._fullScreenRestore = null; /** * The _actual_ game dimensions, as initially set or set by {@link #setGameSize}. * @property {Phaser.Rectangle} _gameSize * @private */ this._gameSize = new Phaser.Rectangle(); /** * The user-supplied scale factor, used with the USER_SCALE scaling mode. * @property {Phaser.Point} _userScaleFactor * @private */ this._userScaleFactor = new Phaser.Point(1, 1); /** * The user-supplied scale trim, used with the USER_SCALE scaling mode. * @property {Phaser.Point} _userScaleTrim * @private */ this._userScaleTrim = new Phaser.Point(0, 0); /** * The last time the bounds were checked in `preUpdate`. * @property {number} _lastUpdate * @private */ this._lastUpdate = 0; /** * Size checks updates are delayed according to the throttle. * The throttle increases to `trackParentInterval` over time and is used to more * rapidly detect changes in certain browsers (eg. IE) while providing back-off safety. * @property {integer} _updateThrottle * @private */ this._updateThrottle = 0; /** * The minimum throttle allowed until it has slowed down sufficiently. * @property {integer} _updateThrottleReset * @private */ this._updateThrottleReset = 100; /** * The cached result of the parent (possibly window) bounds; used to invalidate sizing. * @property {Phaser.Rectangle} _parentBounds * @private */ this._parentBounds = new Phaser.Rectangle(); /** * Temporary bounds used for internal work to cut down on new objects created. * @property {Phaser.Rectangle} _parentBounds * @private */ this._tempBounds = new Phaser.Rectangle(); /** * The Canvas size at which the last onSizeChange signal was triggered. * @property {Phaser.Rectangle} _lastReportedCanvasSize * @private */ this._lastReportedCanvasSize = new Phaser.Rectangle(); /** * The Game size at which the last onSizeChange signal was triggered. * @property {Phaser.Rectangle} _lastReportedGameSize * @private */ this._lastReportedGameSize = new Phaser.Rectangle(); /** * @property {boolean} _booted - ScaleManager booted state. * @private */ this._booted = false; if (game.config) { this.parseConfig(game.config); } this.setupScale(width, height); }; /** * A scale mode that stretches content to fill all available space - see {@link Phaser.ScaleManager#scaleMode scaleMode}. * * @constant * @type {integer} */ Phaser.ScaleManager.EXACT_FIT = 0; /** * A scale mode that prevents any scaling - see {@link Phaser.ScaleManager#scaleMode scaleMode}. * * @constant * @type {integer} */ Phaser.ScaleManager.NO_SCALE = 1; /** * A scale mode that shows the entire game while maintaining proportions - see {@link Phaser.ScaleManager#scaleMode scaleMode}. * * @constant * @type {integer} */ Phaser.ScaleManager.SHOW_ALL = 2; /** * A scale mode that causes the Game size to change - see {@link Phaser.ScaleManager#scaleMode scaleMode}. * * @constant * @type {integer} */ Phaser.ScaleManager.RESIZE = 3; /** * A scale mode that allows a custom scale factor - see {@link Phaser.ScaleManager#scaleMode scaleMode}. * * @constant * @type {integer} */ Phaser.ScaleManager.USER_SCALE = 4; Phaser.ScaleManager.prototype = { /** * Start the ScaleManager. * * @method Phaser.ScaleManager#boot * @protected */ boot: function () { // Configure device-dependent compatibility var compat = this.compatibility; compat.supportsFullScreen = this.game.device.fullscreen && !this.game.device.cocoonJS; // We can't do anything about the status bars in iPads, web apps or desktops if (!this.game.device.iPad && !this.game.device.webApp && !this.game.device.desktop) { if (this.game.device.android && !this.game.device.chrome) { compat.scrollTo = new Phaser.Point(0, 1); } else { compat.scrollTo = new Phaser.Point(0, 0); } } if (this.game.device.desktop) { compat.orientationFallback = 'screen'; compat.clickTrampoline = 'when-not-mouse'; } else { compat.orientationFallback = ''; compat.clickTrampoline = ''; } // Configure event listeners var _this = this; this._orientationChange = function(event) { return _this.orientationChange(event); }; this._windowResize = function(event) { return _this.windowResize(event); }; // This does not appear to be on the standards track window.addEventListener('orientationchange', this._orientationChange, false); window.addEventListener('resize', this._windowResize, false); if (this.compatibility.supportsFullScreen) { this._fullScreenChange = function(event) { return _this.fullScreenChange(event); }; this._fullScreenError = function(event) { return _this.fullScreenError(event); }; document.addEventListener('webkitfullscreenchange', this._fullScreenChange, false); document.addEventListener('mozfullscreenchange', this._fullScreenChange, false); document.addEventListener('MSFullscreenChange', this._fullScreenChange, false); document.addEventListener('fullscreenchange', this._fullScreenChange, false); document.addEventListener('webkitfullscreenerror', this._fullScreenError, false); document.addEventListener('mozfullscreenerror', this._fullScreenError, false); document.addEventListener('MSFullscreenError', this._fullScreenError, false); document.addEventListener('fullscreenerror', this._fullScreenError, false); } this.game.onResume.add(this._gameResumed, this); // Initialize core bounds this.dom.getOffset(this.game.canvas, this.offset); this.bounds.setTo(this.offset.x, this.offset.y, this.width, this.height); this.setGameSize(this.game.width, this.game.height); // Don't use updateOrientationState so events are not fired this.screenOrientation = this.dom.getScreenOrientation(this.compatibility.orientationFallback); if (Phaser.FlexGrid) { this.grid = new Phaser.FlexGrid(this, this.width, this.height); } this._booted = true; if (this._pendingScaleMode !== null) { this.scaleMode = this._pendingScaleMode; this._pendingScaleMode = null; } }, /** * Load configuration settings. * * @method Phaser.ScaleManager#parseConfig * @protected * @param {object} config - The game configuration object. */ parseConfig: function (config) { if (config['scaleMode'] !== undefined) { if (this._booted) { this.scaleMode = config['scaleMode']; } else { this._pendingScaleMode = config['scaleMode']; } } if (config['fullScreenScaleMode'] !== undefined) { this.fullScreenScaleMode = config['fullScreenScaleMode']; } if (config['fullScreenTarget']) { this.fullScreenTarget = config['fullScreenTarget']; } }, /** * Calculates and sets the game dimensions based on the given width and height. * * This should _not_ be called when in fullscreen mode. * * @method Phaser.ScaleManager#setupScale * @protected * @param {number|string} width - The width of the game. * @param {number|string} height - The height of the game. */ setupScale: function (width, height) { var target; var rect = new Phaser.Rectangle(); if (this.game.parent !== '') { if (typeof this.game.parent === 'string') { // hopefully an element ID target = document.getElementById(this.game.parent); } else if (this.game.parent && this.game.parent.nodeType === 1) { // quick test for a HTMLelement target = this.game.parent; } } // Fallback, covers an invalid ID and a non HTMLelement object if (!target) { // Use the full window this.parentNode = null; this.parentIsWindow = true; rect.width = this.dom.visualBounds.width; rect.height = this.dom.visualBounds.height; this.offset.set(0, 0); } else { this.parentNode = target; this.parentIsWindow = false; this.getParentBounds(this._parentBounds); rect.width = this._parentBounds.width; rect.height = this._parentBounds.height; this.offset.set(this._parentBounds.x, this._parentBounds.y); } var newWidth = 0; var newHeight = 0; if (typeof width === 'number') { newWidth = width; } else { // Percentage based this.parentScaleFactor.x = parseInt(width, 10) / 100; newWidth = rect.width * this.parentScaleFactor.x; } if (typeof height === 'number') { newHeight = height; } else { // Percentage based this.parentScaleFactor.y = parseInt(height, 10) / 100; newHeight = rect.height * this.parentScaleFactor.y; } newWidth = Math.floor(newWidth); newHeight = Math.floor(newHeight); this._gameSize.setTo(0, 0, newWidth, newHeight); this.updateDimensions(newWidth, newHeight, false); }, /** * Invoked when the game is resumed. * * @method Phaser.ScaleManager#_gameResumed * @private */ _gameResumed: function () { this.queueUpdate(true); }, /** * Set the actual Game size. * Use this instead of directly changing `game.width` or `game.height`. * * The actual physical display (Canvas element size) depends on various settings including * - Scale mode * - Scaling factor * - Size of Canvas's parent element or CSS rules such as min-height/max-height; * - The size of the Window * * @method Phaser.ScaleManager#setGameSize * @public * @param {integer} width - _Game width_, in pixels. * @param {integer} height - _Game height_, in pixels. */ setGameSize: function (width, height) { this._gameSize.setTo(0, 0, width, height); if (this.currentScaleMode !== Phaser.ScaleManager.RESIZE) { this.updateDimensions(width, height, true); } this.queueUpdate(true); }, /** * Set a User scaling factor used in the USER_SCALE scaling mode. * * The target canvas size is computed by: * * canvas.width = (game.width * hScale) - hTrim * canvas.height = (game.height * vScale) - vTrim * * This method can be used in the {@link Phaser.ScaleManager#setResizeCallback resize callback}. * * @method Phaser.ScaleManager#setUserScale * @param {number} hScale - Horizontal scaling factor. * @param {numer} vScale - Vertical scaling factor. * @param {integer} [hTrim=0] - Horizontal trim, applied after scaling. * @param {integer} [vTrim=0] - Vertical trim, applied after scaling. */ setUserScale: function (hScale, vScale, hTrim, vTrim) { this._userScaleFactor.setTo(hScale, vScale); this._userScaleTrim.setTo(hTrim | 0, vTrim | 0); this.queueUpdate(true); }, /** * Sets the callback that will be invoked before sizing calculations. * * This is the appropriate place to call {@link #setUserScale} if needing custom dynamic scaling. * * The callback is supplied with two arguments `scale` and `parentBounds` where `scale` is the ScaleManager * and `parentBounds`, a Phaser.Rectangle, is the size of the Parent element. * * This callback * - May be invoked even though the parent container or canvas sizes have not changed * - Unlike {@link #onSizeChange}, it runs _before_ the canvas is guaranteed to be updated * - Will be invoked from `preUpdate`, _even when_ the game is paused * * See {@link #onSizeChange} for a better way of reacting to layout updates. * * @method Phaser.ScaleManager#setResizeCallback * @public * @param {function} callback - The callback that will be called each time a window.resize event happens or if set, the parent container resizes. * @param {object} context - The context in which the callback will be called. */ setResizeCallback: function (callback, context) { this.onResize = callback; this.onResizeContext = context; }, /** * Signals a resize - IF the canvas or Game size differs from the last signal. * * This also triggers updates on {@link #grid} (FlexGrid) and, if in a RESIZE mode, `game.state` (StateManager). * * @method Phaser.ScaleManager#signalSizeChange * @private */ signalSizeChange: function () { if (!Phaser.Rectangle.sameDimensions(this, this._lastReportedCanvasSize) || !Phaser.Rectangle.sameDimensions(this.game, this._lastReportedGameSize)) { var width = this.width; var height = this.height; this._lastReportedCanvasSize.setTo(0, 0, width, height); this._lastReportedGameSize.setTo(0, 0, this.game.width, this.game.height); if (this.grid) { this.grid.onResize(width, height); } this.onSizeChange.dispatch(this, width, height); // Per StateManager#onResizeCallback, it only occurs when in RESIZE mode. if (this.currentScaleMode === Phaser.ScaleManager.RESIZE) { this.game.state.resize(width, height); this.game.load.resize(width, height); } } }, /** * Set the min and max dimensions for the Display canvas. * * _Note:_ The min/max dimensions are only applied in some cases * - When the device is not in an incorrect orientation; or * - The scale mode is EXACT_FIT when not in fullscreen * * @method Phaser.ScaleManager#setMinMax * @public * @param {number} minWidth - The minimum width the game is allowed to scale down to. * @param {number} minHeight - The minimum height the game is allowed to scale down to. * @param {number} [maxWidth] - The maximum width the game is allowed to scale up to; only changed if specified. * @param {number} [maxHeight] - The maximum height the game is allowed to scale up to; only changed if specified. * @todo These values are only sometimes honored. */ setMinMax: function (minWidth, minHeight, maxWidth, maxHeight) { this.minWidth = minWidth; this.minHeight = minHeight; if (typeof maxWidth !== 'undefined') { this.maxWidth = maxWidth; } if (typeof maxHeight !== 'undefined') { this.maxHeight = maxHeight; } }, /** * The ScaleManager.preUpdate is called automatically by the core Game loop. * * @method Phaser.ScaleManager#preUpdate * @protected */ preUpdate: function () { if (this.game.time.time < (this._lastUpdate + this._updateThrottle)) { return; } var prevThrottle = this._updateThrottle; this._updateThrottleReset = prevThrottle >= 400 ? 0 : 100; this.dom.getOffset(this.game.canvas, this.offset); var prevWidth = this._parentBounds.width; var prevHeight = this._parentBounds.height; var bounds = this.getParentBounds(this._parentBounds); var boundsChanged = bounds.width !== prevWidth || bounds.height !== prevHeight; // Always invalidate on a newly detected orientation change var orientationChanged = this.updateOrientationState(); if (boundsChanged || orientationChanged) { if (this.onResize) { this.onResize.call(this.onResizeContext, this, bounds); } this.updateLayout(); this.signalSizeChange(); } // Next throttle, eg. 25, 50, 100, 200.. var throttle = this._updateThrottle * 2; // Don't let an update be too eager about resetting the throttle. if (this._updateThrottle < prevThrottle) { throttle = Math.min(prevThrottle, this._updateThrottleReset); } this._updateThrottle = Phaser.Math.clamp(throttle, 25, this.trackParentInterval); this._lastUpdate = this.game.time.time; }, /** * Update method while paused. * * @method Phaser.ScaleManager#pauseUpdate * @private */ pauseUpdate: function () { this.preUpdate(); // Updates at slowest. this._updateThrottle = this.trackParentInterval; }, /** * Update the dimensions taking the parent scaling factor into account. * * @method Phaser.ScaleManager#updateDimensions * @private * @param {number} width - The new width of the parent container. * @param {number} height - The new height of the parent container. * @param {boolean} resize - True if the renderer should be resized, otherwise false to just update the internal vars. */ updateDimensions: function (width, height, resize) { this.width = width * this.parentScaleFactor.x; this.height = height * this.parentScaleFactor.y; this.game.width = this.width; this.game.height = this.height; this.sourceAspectRatio = this.width / this.height; this.updateScalingAndBounds(); if (resize) { // Resize the renderer (which in turn resizes the Display canvas!) this.game.renderer.resize(this.width, this.height); // The Camera can never be smaller than the Game size this.game.camera.setSize(this.width, this.height); // This should only happen if the world is smaller than the new canvas size this.game.world.resize(this.width, this.height); } }, /** * Update relevant scaling values based on the ScaleManager dimension and game dimensions, * which should already be set. This does not change {@link #sourceAspectRatio}. * * @method Phaser.ScaleManager#updateScalingAndBounds * @private */ updateScalingAndBounds: function () { this.scaleFactor.x = this.game.width / this.width; this.scaleFactor.y = this.game.height / this.height; this.scaleFactorInversed.x = this.width / this.game.width; this.scaleFactorInversed.y = this.height / this.game.height; this.aspectRatio = this.width / this.height; // This can be invoked in boot pre-canvas if (this.game.canvas) { this.dom.getOffset(this.game.canvas, this.offset); } this.bounds.setTo(this.offset.x, this.offset.y, this.width, this.height); // Can be invoked in boot pre-input if (this.game.input && this.game.input.scale) { this.game.input.scale.setTo(this.scaleFactor.x, this.scaleFactor.y); } }, /** * Force the game to run in only one orientation. * * This enables generation of incorrect orientation signals and affects resizing but does not otherwise rotate or lock the orientation. * * Orientation checks are performed via the Screen Orientation API, if available in browser. This means it will check your monitor * orientation on desktop, or your device orientation on mobile, rather than comparing actual game dimensions. If you need to check the * viewport dimensions instead and bypass the Screen Orientation API then set: `ScaleManager.compatibility.orientationFallback = 'viewport'` * * @method Phaser.ScaleManager#forceOrientation * @public * @param {boolean} forceLandscape - true if the game should run in landscape mode only. * @param {boolean} [forcePortrait=false] - true if the game should run in portrait mode only. */ forceOrientation: function (forceLandscape, forcePortrait) { if (forcePortrait === undefined) { forcePortrait = false; } this.forceLandscape = forceLandscape; this.forcePortrait = forcePortrait; this.queueUpdate(true); }, /** * Classify the orientation, per `getScreenOrientation`. * * @method Phaser.ScaleManager#classifyOrientation * @private * @param {string} orientation - The orientation string, e.g. 'portrait-primary'. * @return {?string} The classified orientation: 'portrait', 'landscape`, or null. */ classifyOrientation: function (orientation) { if (orientation === 'portrait-primary' || orientation === 'portrait-secondary') { return 'portrait'; } else if (orientation === 'landscape-primary' || orientation === 'landscape-secondary') { return 'landscape'; } else { return null; } }, /** * Updates the current orientation and dispatches orientation change events. * * @method Phaser.ScaleManager#updateOrientationState * @private * @return {boolean} True if the orientation state changed which means a forced update is likely required. */ updateOrientationState: function () { var previousOrientation = this.screenOrientation; var previouslyIncorrect = this.incorrectOrientation; this.screenOrientation = this.dom.getScreenOrientation(this.compatibility.orientationFallback); this.incorrectOrientation = (this.forceLandscape && !this.isLandscape) || (this.forcePortrait && !this.isPortrait); var changed = previousOrientation !== this.screenOrientation; var correctnessChanged = previouslyIncorrect !== this.incorrectOrientation; if (correctnessChanged) { if (this.incorrectOrientation) { this.enterIncorrectOrientation.dispatch(); } else { this.leaveIncorrectOrientation.dispatch(); } } if (changed || correctnessChanged) { this.onOrientationChange.dispatch(this, previousOrientation, previouslyIncorrect); } return changed || correctnessChanged; }, /** * window.orientationchange event handler. * * @method Phaser.ScaleManager#orientationChange * @private * @param {Event} event - The orientationchange event data. */ orientationChange: function (event) { this.event = event; this.queueUpdate(true); }, /** * window.resize event handler. * * @method Phaser.ScaleManager#windowResize * @private * @param {Event} event - The resize event data. */ windowResize: function (event) { this.event = event; this.queueUpdate(true); }, /** * Scroll to the top - in some environments. See `compatibility.scrollTo`. * * @method Phaser.ScaleManager#scrollTop * @private */ scrollTop: function () { var scrollTo = this.compatibility.scrollTo; if (scrollTo) { window.scrollTo(scrollTo.x, scrollTo.y); } }, /** * The "refresh" methods informs the ScaleManager that a layout refresh is required. * * The ScaleManager automatically queues a layout refresh (eg. updates the Game size or Display canvas layout) * when the browser is resized, the orientation changes, or when there is a detected change * of the Parent size. Refreshing is also done automatically when public properties, * such as {@link #scaleMode}, are updated or state-changing methods are invoked. * * The "refresh" method _may_ need to be used in a few (rare) situtations when * * - a device change event is not correctly detected; or * - the Parent size changes (and an immediate reflow is desired); or * - the ScaleManager state is updated by non-standard means; or * - certain {@link #compatibility} properties are manually changed. * * The queued layout refresh is not immediate but will run promptly in an upcoming `preRender`. * * @method Phaser.ScaleManager#refresh * @public */ refresh: function () { this.scrollTop(); this.queueUpdate(true); }, /** * Updates the game / canvas position and size. * * @method Phaser.ScaleManager#updateLayout * @private */ updateLayout: function () { var scaleMode = this.currentScaleMode; if (scaleMode === Phaser.ScaleManager.RESIZE) { this.reflowGame(); return; } this.scrollTop(); if (this.compatibility.forceMinimumDocumentHeight) { // (This came from older code, by why is it here?) // Set minimum height of content to new window height document.documentElement.style.minHeight = window.innerHeight + 'px'; } if (this.incorrectOrientation) { this.setMaximum(); } else { if (scaleMode === Phaser.ScaleManager.EXACT_FIT) { this.setExactFit(); } else if (scaleMode === Phaser.ScaleManager.SHOW_ALL) { if (!this.isFullScreen && this.boundingParent && this.compatibility.canExpandParent) { // Try to expand parent out, but choosing maximizing dimensions. // Then select minimize dimensions which should then honor parent // maximum bound applications. this.setShowAll(true); this.resetCanvas(); this.setShowAll(); } else { this.setShowAll(); } } else if (scaleMode === Phaser.ScaleManager.NO_SCALE) { this.width = this.game.width; this.height = this.game.height; } else if (scaleMode === Phaser.ScaleManager.USER_SCALE) { this.width = (this.game.width * this._userScaleFactor.x) - this._userScaleTrim.x; this.height = (this.game.height * this._userScaleFactor.y) - this._userScaleTrim.y; } } if (!this.compatibility.canExpandParent && (scaleMode === Phaser.ScaleManager.SHOW_ALL || scaleMode === Phaser.ScaleManager.USER_SCALE)) { var bounds = this.getParentBounds(this._tempBounds); this.width = Math.min(this.width, bounds.width); this.height = Math.min(this.height, bounds.height); } // Always truncate / force to integer this.width = this.width | 0; this.height = this.height | 0; this.reflowCanvas(); }, /** * Returns the computed Parent size/bounds that the Display canvas is allowed/expected to fill. * * If in fullscreen mode or without parent (see {@link #parentIsWindow}), * this will be the bounds of the visual viewport itself. * * This function takes the {@link #windowConstraints} into consideration - if the parent is partially outside * the viewport then this function may return a smaller than expected size. * * Values are rounded to the nearest pixel. * * @method Phaser.ScaleManager#getParentBounds * @protected * @param {Phaser.Rectangle} [target=(new Rectangle)] - The rectangle to update; a new one is created as needed. * @return {Phaser.Rectangle} The established parent bounds. */ getParentBounds: function (target) { var bounds = target || new Phaser.Rectangle(); var parentNode = this.boundingParent; var visualBounds = this.dom.visualBounds; var layoutBounds = this.dom.layoutBounds; if (!parentNode) { bounds.setTo(0, 0, visualBounds.width, visualBounds.height); } else { // Ref. http://msdn.microsoft.com/en-us/library/hh781509(v=vs.85).aspx for getBoundingClientRect var clientRect = parentNode.getBoundingClientRect(); var parentRect = (parentNode.offsetParent) ? parentNode.offsetParent.getBoundingClientRect() : parentNode.getBoundingClientRect(); bounds.setTo(clientRect.left - parentRect.left, clientRect.top - parentRect.top, clientRect.width, clientRect.height); var wc = this.windowConstraints; if (wc.right) { var windowBounds = wc.right === 'layout' ? layoutBounds : visualBounds; bounds.right = Math.min(bounds.right, windowBounds.width); } if (wc.bottom) { var windowBounds = wc.bottom === 'layout' ? layoutBounds : visualBounds; bounds.bottom = Math.min(bounds.bottom, windowBounds.height); } } bounds.setTo( Math.round(bounds.x), Math.round(bounds.y), Math.round(bounds.width), Math.round(bounds.height)); return bounds; }, /** * Update the canvas position/margins - for alignment within the parent container. * * The canvas margins _must_ be reset/cleared prior to invoking this. * * @method Phaser.ScaleManager#alignCanvas * @private * @param {boolean} horizontal - Align horizontally? * @param {boolean} vertical - Align vertically? */ alignCanvas: function (horizontal, vertical) { var parentBounds = this.getParentBounds(this._tempBounds); var canvas = this.game.canvas; var margin = this.margin; if (horizontal) { margin.left = margin.right = 0; var canvasBounds = canvas.getBoundingClientRect(); if (this.width < parentBounds.width && !this.incorrectOrientation) { var currentEdge = canvasBounds.left - parentBounds.x; var targetEdge = (parentBounds.width / 2) - (this.width / 2); targetEdge = Math.max(targetEdge, 0); var offset = targetEdge - currentEdge; margin.left = Math.round(offset); } canvas.style.marginLeft = margin.left + 'px'; if (margin.left !== 0) { margin.right = -(parentBounds.width - canvasBounds.width - margin.left); canvas.style.marginRight = margin.right + 'px'; } } if (vertical) { margin.top = margin.bottom = 0; var canvasBounds = canvas.getBoundingClientRect(); if (this.height < parentBounds.height && !this.incorrectOrientation) { var currentEdge = canvasBounds.top - parentBounds.y; var targetEdge = (parentBounds.height / 2) - (this.height / 2); targetEdge = Math.max(targetEdge, 0); var offset = targetEdge - currentEdge; margin.top = Math.round(offset); } canvas.style.marginTop = margin.top + 'px'; if (margin.top !== 0) { margin.bottom = -(parentBounds.height - canvasBounds.height - margin.top); canvas.style.marginBottom = margin.bottom + 'px'; } } // Silly backwards compatibility.. margin.x = margin.left; margin.y = margin.top; }, /** * Updates the Game state / size. * * The canvas margins may always be adjusted, even if alignment is not in effect. * * @method Phaser.ScaleManager#reflowGame * @private */ reflowGame: function () { this.resetCanvas('', ''); var bounds = this.getParentBounds(this._tempBounds); this.updateDimensions(bounds.width, bounds.height, true); }, /** * Updates the Display canvas size. * * The canvas margins may always be adjusted, even alignment is not in effect. * * @method Phaser.ScaleManager#reflowCanvas * @private */ reflowCanvas: function () { if (!this.incorrectOrientation) { this.width = Phaser.Math.clamp(this.width, this.minWidth || 0, this.maxWidth || this.width); this.height = Phaser.Math.clamp(this.height, this.minHeight || 0, this.maxHeight || this.height); } this.resetCanvas(); if (!this.compatibility.noMargins) { if (this.isFullScreen && this._createdFullScreenTarget) { this.alignCanvas(true, true); } else { this.alignCanvas(this.pageAlignHorizontally, this.pageAlignVertically); } } this.updateScalingAndBounds(); }, /** * "Reset" the Display canvas and set the specified width/height. * * @method Phaser.ScaleManager#resetCanvas * @private * @param {string} [cssWidth=(current width)] - The css width to set. * @param {string} [cssHeight=(current height)] - The css height to set. */ resetCanvas: function (cssWidth, cssHeight) { if (cssWidth === undefined) { cssWidth = this.width + 'px'; } if (cssHeight === undefined) { cssHeight = this.height + 'px'; } var canvas = this.game.canvas; if (!this.compatibility.noMargins) { canvas.style.marginLeft = ''; canvas.style.marginTop = ''; canvas.style.marginRight = ''; canvas.style.marginBottom = ''; } canvas.style.width = cssWidth; canvas.style.height = cssHeight; }, /** * Queues/marks a size/bounds check as needing to occur (from `preUpdate`). * * @method Phaser.ScaleManager#queueUpdate * @private * @param {boolean} force - If true resets the parent bounds to ensure the check is dirty. */ queueUpdate: function (force) { if (force) { this._parentBounds.width = 0; this._parentBounds.height = 0; } this._updateThrottle = this._updateThrottleReset; }, /** * Reset internal data/state. * * @method Phaser.ScaleManager#reset * @private */ reset: function (clearWorld) { if (clearWorld && this.grid) { this.grid.reset(); } }, /** * Updates the width/height to that of the window. * * @method Phaser.ScaleManager#setMaximum * @private */ setMaximum: function () { this.width = this.dom.visualBounds.width; this.height = this.dom.visualBounds.height; }, /** * Updates the width/height such that the game is scaled proportionally. * * @method Phaser.ScaleManager#setShowAll * @private * @param {boolean} expanding - If true then the maximizing dimension is chosen. */ setShowAll: function (expanding) { var bounds = this.getParentBounds(this._tempBounds); var width = bounds.width; var height = bounds.height; var multiplier; if (expanding) { multiplier = Math.max((height / this.game.height), (width / this.game.width)); } else { multiplier = Math.min((height / this.game.height), (width / this.game.width)); } this.width = Math.round(this.game.width * multiplier); this.height = Math.round(this.game.height * multiplier); }, /** * Updates the width/height such that the game is stretched to the available size. * Honors {@link #maxWidth} and {@link #maxHeight} when _not_ in fullscreen. * * @method Phaser.ScaleManager#setExactFit * @private */ setExactFit: function () { var bounds = this.getParentBounds(this._tempBounds); this.width = bounds.width; this.height = bounds.height; if (this.isFullScreen) { // Max/min not honored fullscreen return; } if (this.maxWidth) { this.width = Math.min(this.width, this.maxWidth); } if (this.maxHeight) { this.height = Math.min(this.height, this.maxHeight); } }, /** * Creates a fullscreen target. This is called automatically as as needed when entering * fullscreen mode and the resulting element is supplied to {@link #onFullScreenInit}. * * Use {@link #onFullScreenInit} to customize the created object. * * @method Phaser.ScaleManager#createFullScreenTarget * @protected */ createFullScreenTarget: function () { var fsTarget = document.createElement('div'); fsTarget.style.margin = '0'; fsTarget.style.padding = '0'; fsTarget.style.background = '#000'; return fsTarget; }, /** * Start the browsers fullscreen mode - this _must_ be called from a user input Pointer or Mouse event. * * The Fullscreen API must be supported by the browser for this to work - it is not the same as setting * the game size to fill the browser window. See {@link Phaser.ScaleManager#compatibility compatibility.supportsFullScreen} to check if the current * device is reported to support fullscreen mode. * * The {@link #fullScreenFailed} signal will be dispatched if the fullscreen change request failed or the game does not support the Fullscreen API. * * @method Phaser.ScaleManager#startFullScreen * @public * @param {boolean} [antialias] - Changes the anti-alias feature of the canvas before jumping in to fullscreen (false = retain pixel art, true = smooth art). If not specified then no change is made. Only works in CANVAS mode. * @param {boolean} [allowTrampoline=undefined] - Internal argument. If `false` click trampolining is suppressed. * @return {boolean} Returns true if the device supports fullscreen mode and fullscreen mode was attempted to be started. (It might not actually start, wait for the signals.) */ startFullScreen: function (antialias, allowTrampoline) { if (this.isFullScreen) { return false; } if (!this.compatibility.supportsFullScreen) { // Error is called in timeout to emulate the real fullscreenerror event better var _this = this; setTimeout(function () { _this.fullScreenError(); }, 10); return; } if (this.compatibility.clickTrampoline === 'when-not-mouse') { var input = this.game.input; if (input.activePointer && input.activePointer !== input.mousePointer && (allowTrampoline || allowTrampoline !== false)) { input.activePointer.addClickTrampoline("startFullScreen", this.startFullScreen, this, [antialias, false]); return; } } if (typeof antialias !== 'undefined' && this.game.renderType === Phaser.CANVAS) { this.game.stage.smoothed = antialias; } var fsTarget = this.fullScreenTarget; if (!fsTarget) { this.cleanupCreatedTarget(); this._createdFullScreenTarget = this.createFullScreenTarget(); fsTarget = this._createdFullScreenTarget; } var initData = { targetElement: fsTarget }; this.onFullScreenInit.dispatch(this, initData); if (this._createdFullScreenTarget) { // Move the Display canvas inside of the target and add the target to the DOM // (The target has to be added for the Fullscreen API to work.) var canvas = this.game.canvas; var parent = canvas.parentNode; parent.insertBefore(fsTarget, canvas); fsTarget.appendChild(canvas); } if (this.game.device.fullscreenKeyboard) { fsTarget[this.game.device.requestFullscreen](Element.ALLOW_KEYBOARD_INPUT); } else { fsTarget[this.game.device.requestFullscreen](); } return true; }, /** * Stops / exits fullscreen mode, if active. * * @method Phaser.ScaleManager#stopFullScreen * @public * @return {boolean} Returns true if the browser supports fullscreen mode and fullscreen mode will be exited. */ stopFullScreen: function () { if (!this.isFullScreen || !this.compatibility.supportsFullScreen) { return false; } document[this.game.device.cancelFullscreen](); return true; }, /** * Cleans up the previous fullscreen target, if such was automatically created. * This ensures the canvas is restored to its former parent, assuming the target didn't move. * * @method Phaser.ScaleManager#cleanupCreatedTarget * @private */ cleanupCreatedTarget: function () { var fsTarget = this._createdFullScreenTarget; if (fsTarget && fsTarget.parentNode) { // Make sure to cleanup synthetic target for sure; // swap the canvas back to the parent. var parent = fsTarget.parentNode; parent.insertBefore(this.game.canvas, fsTarget); parent.removeChild(fsTarget); } this._createdFullScreenTarget = null; }, /** * Used to prepare/restore extra fullscreen mode settings. * (This does move any elements within the DOM tree.) * * @method Phaser.ScaleManager#prepScreenMode * @private * @param {boolean} enteringFullscreen - True if _entering_ fullscreen, false if _leaving_. */ prepScreenMode: function (enteringFullscreen) { var createdTarget = !!this._createdFullScreenTarget; var fsTarget = this._createdFullScreenTarget || this.fullScreenTarget; if (enteringFullscreen) { if (createdTarget || this.fullScreenScaleMode === Phaser.ScaleManager.EXACT_FIT) { // Resize target, as long as it's not the canvas if (fsTarget !== this.game.canvas) { this._fullScreenRestore = { targetWidth: fsTarget.style.width, targetHeight: fsTarget.style.height }; fsTarget.style.width = '100%'; fsTarget.style.height = '100%'; } } } else { // Have restore information if (this._fullScreenRestore) { fsTarget.style.width = this._fullScreenRestore.targetWidth; fsTarget.style.height = this._fullScreenRestore.targetHeight; this._fullScreenRestore = null; } // Always reset to game size this.updateDimensions(this._gameSize.width, this._gameSize.height, true); this.resetCanvas(); } }, /** * Called automatically when the browser enters of leaves fullscreen mode. * * @method Phaser.ScaleManager#fullScreenChange * @private * @param {Event} [event=undefined] - The fullscreenchange event */ fullScreenChange: function (event) { this.event = event; if (this.isFullScreen) { this.prepScreenMode(true); this.updateLayout(); this.queueUpdate(true); } else { this.prepScreenMode(false); this.cleanupCreatedTarget(); this.updateLayout(); this.queueUpdate(true); } this.onFullScreenChange.dispatch(this, this.width, this.height); }, /** * Called automatically when the browser fullscreen request fails; * or called when a fullscreen request is made on a device for which it is not supported. * * @method Phaser.ScaleManager#fullScreenError * @private * @param {Event} [event=undefined] - The fullscreenerror event; undefined if invoked on a device that does not support the Fullscreen API. */ fullScreenError: function (event) { this.event = event; this.cleanupCreatedTarget(); console.warn('Phaser.ScaleManager: requestFullscreen failed or device does not support the Fullscreen API'); this.onFullScreenError.dispatch(this); }, /** * Takes a Sprite or Image object and scales it to fit the given dimensions. * Scaling happens proportionally without distortion to the sprites texture. * The letterBox parameter controls if scaling will produce a letter-box effect or zoom the * sprite until it fills the given values. Note that with letterBox set to false the scaled sprite may spill out over either * the horizontal or vertical sides of the target dimensions. If you wish to stop this you can crop the Sprite. * * @method Phaser.ScaleManager#scaleSprite * @protected * @param {Phaser.Sprite|Phaser.Image} sprite - The sprite we want to scale. * @param {integer} [width] - The target width that we want to fit the sprite in to. If not given it defaults to ScaleManager.width. * @param {integer} [height] - The target height that we want to fit the sprite in to. If not given it defaults to ScaleManager.height. * @param {boolean} [letterBox=false] - True if we want the `fitted` mode. Otherwise, the function uses the `zoom` mode. * @return {Phaser.Sprite|Phaser.Image} The scaled sprite. */ scaleSprite: function (sprite, width, height, letterBox) { if (width === undefined) { width = this.width; } if (height === undefined) { height = this.height; } if (letterBox === undefined) { letterBox = false; } if (!sprite || !sprite['scale']) { return sprite; } sprite.scale.x = 1; sprite.scale.y = 1; if ((sprite.width <= 0) || (sprite.height <= 0) || (width <= 0) || (height <= 0)) { return sprite; } var scaleX1 = width; var scaleY1 = (sprite.height * width) / sprite.width; var scaleX2 = (sprite.width * height) / sprite.height; var scaleY2 = height; var scaleOnWidth = (scaleX2 > width); if (scaleOnWidth) { scaleOnWidth = letterBox; } else { scaleOnWidth = !letterBox; } if (scaleOnWidth) { sprite.width = Math.floor(scaleX1); sprite.height = Math.floor(scaleY1); } else { sprite.width = Math.floor(scaleX2); sprite.height = Math.floor(scaleY2); } // Enable at some point? // sprite.x = Math.floor((width - sprite.width) / 2); // sprite.y = Math.floor((height - sprite.height) / 2); return sprite; }, /** * Destroys the ScaleManager and removes any event listeners. * This should probably only be called when the game is destroyed. * * @method Phaser.ScaleManager#destroy * @protected */ destroy: function () { this.game.onResume.remove(this._gameResumed, this); window.removeEventListener('orientationchange', this._orientationChange, false); window.removeEventListener('resize', this._windowResize, false); if (this.compatibility.supportsFullScreen) { document.removeEventListener('webkitfullscreenchange', this._fullScreenChange, false); document.removeEventListener('mozfullscreenchange', this._fullScreenChange, false); document.removeEventListener('MSFullscreenChange', this._fullScreenChange, false); document.removeEventListener('fullscreenchange', this._fullScreenChange, false); document.removeEventListener('webkitfullscreenerror', this._fullScreenError, false); document.removeEventListener('mozfullscreenerror', this._fullScreenError, false); document.removeEventListener('MSFullscreenError', this._fullScreenError, false); document.removeEventListener('fullscreenerror', this._fullScreenError, false); } } }; Phaser.ScaleManager.prototype.constructor = Phaser.ScaleManager; /** * The DOM element that is considered the Parent bounding element, if any. * * This `null` if {@link #parentIsWindow} is true or if fullscreen mode is entered and {@link #fullScreenTarget} is specified. * It will also be null if there is no game canvas or if the game canvas has no parent. * * @name Phaser.ScaleManager#boundingParent * @property {?DOMElement} boundingParent * @readonly */ Object.defineProperty(Phaser.ScaleManager.prototype, "boundingParent", { get: function () { if (this.parentIsWindow || (this.isFullScreen && !this._createdFullScreenTarget)) { return null; } var parentNode = this.game.canvas && this.game.canvas.parentNode; return parentNode || null; } }); /** * The scaling method used by the ScaleManager when not in fullscreen. * * <dl> * <dt>{@link Phaser.ScaleManager.NO_SCALE}</dt> * <dd> * The Game display area will not be scaled - even if it is too large for the canvas/screen. * This mode _ignores_ any applied scaling factor and displays the canvas at the Game size. * </dd> * <dt>{@link Phaser.ScaleManager.EXACT_FIT}</dt> * <dd> * The Game display area will be _stretched_ to fill the entire size of the canvas's parent element and/or screen. * Proportions are not maintained. * </dd> * <dt>{@link Phaser.ScaleManager.SHOW_ALL}</dt> * <dd> * Show the entire game display area while _maintaining_ the original aspect ratio. * </dd> * <dt>{@link Phaser.ScaleManager.RESIZE}</dt> * <dd> * The dimensions of the game display area are changed to match the size of the parent container. * That is, this mode _changes the Game size_ to match the display size. * <p> * Any manually set Game size (see {@link #setGameSize}) is ignored while in effect. * </dd> * <dt>{@link Phaser.ScaleManager.USER_SCALE}</dt> * <dd> * The game Display is scaled according to the user-specified scale set by {@link Phaser.ScaleManager#setUserScale setUserScale}. * <p> * This scale can be adjusted in the {@link Phaser.ScaleManager#setResizeCallback resize callback} * for flexible custom-sizing needs. * </dd> * </dl> * * @name Phaser.ScaleManager#scaleMode * @property {integer} scaleMode */ Object.defineProperty(Phaser.ScaleManager.prototype, "scaleMode", { get: function () { return this._scaleMode; }, set: function (value) { if (value !== this._scaleMode) { if (!this.isFullScreen) { this.updateDimensions(this._gameSize.width, this._gameSize.height, true); this.queueUpdate(true); } this._scaleMode = value; } return this._scaleMode; } }); /** * The scaling method used by the ScaleManager when in fullscreen. * * See {@link Phaser.ScaleManager#scaleMode scaleMode} for the different modes allowed. * * @name Phaser.ScaleManager#fullScreenScaleMode * @property {integer} fullScreenScaleMode */ Object.defineProperty(Phaser.ScaleManager.prototype, "fullScreenScaleMode", { get: function () { return this._fullScreenScaleMode; }, set: function (value) { if (value !== this._fullScreenScaleMode) { // If in fullscreen then need a wee bit more work if (this.isFullScreen) { this.prepScreenMode(false); this._fullScreenScaleMode = value; this.prepScreenMode(true); this.queueUpdate(true); } else { this._fullScreenScaleMode = value; } } return this._fullScreenScaleMode; } }); /** * Returns the current scale mode - for normal or fullscreen operation. * * See {@link Phaser.ScaleManager#scaleMode scaleMode} for the different modes allowed. * * @name Phaser.ScaleManager#currentScaleMode * @property {number} currentScaleMode * @protected * @readonly */ Object.defineProperty(Phaser.ScaleManager.prototype, "currentScaleMode", { get: function () { return this.isFullScreen ? this._fullScreenScaleMode : this._scaleMode; } }); /** * When enabled the Display canvas will be horizontally-aligned _in the Parent container_ (or {@link Phaser.ScaleManager#parentIsWindow window}). * * To align horizontally across the page the Display canvas should be added directly to page; * or the parent container should itself be horizontally aligned. * * Horizontal alignment is not applicable with the {@link .RESIZE} scaling mode. * * @name Phaser.ScaleManager#pageAlignHorizontally * @property {boolean} pageAlignHorizontally * @default false */ Object.defineProperty(Phaser.ScaleManager.prototype, "pageAlignHorizontally", { get: function () { return this._pageAlignHorizontally; }, set: function (value) { if (value !== this._pageAlignHorizontally) { this._pageAlignHorizontally = value; this.queueUpdate(true); } } }); /** * When enabled the Display canvas will be vertically-aligned _in the Parent container_ (or {@link Phaser.ScaleManager#parentIsWindow window}). * * To align vertically the Parent element should have a _non-collapsible_ height, such that it will maintain * a height _larger_ than the height of the contained Game canvas - the game canvas will then be scaled vertically * _within_ the remaining available height dictated by the Parent element. * * One way to prevent the parent from collapsing is to add an absolute "min-height" CSS property to the parent element. * If specifying a relative "min-height/height" or adjusting margins, the Parent height must still be non-collapsible (see note). * * _Note_: In version 2.2 the minimum document height is _not_ automatically set to the viewport/window height. * To automatically update the minimum document height set {@link Phaser.ScaleManager#compatibility compatibility.forceMinimumDocumentHeight} to true. * * Vertical alignment is not applicable with the {@link .RESIZE} scaling mode. * * @name Phaser.ScaleManager#pageAlignVertically * @property {boolean} pageAlignVertically * @default false */ Object.defineProperty(Phaser.ScaleManager.prototype, "pageAlignVertically", { get: function () { return this._pageAlignVertically; }, set: function (value) { if (value !== this._pageAlignVertically) { this._pageAlignVertically = value; this.queueUpdate(true); } } }); /** * Returns true if the browser is in fullscreen mode, otherwise false. * @name Phaser.ScaleManager#isFullScreen * @property {boolean} isFullScreen * @readonly */ Object.defineProperty(Phaser.ScaleManager.prototype, "isFullScreen", { get: function () { return !!(document['fullscreenElement'] || document['webkitFullscreenElement'] || document['mozFullScreenElement'] || document['msFullscreenElement']); } }); /** * Returns true if the screen orientation is in portrait mode. * * @name Phaser.ScaleManager#isPortrait * @property {boolean} isPortrait * @readonly */ Object.defineProperty(Phaser.ScaleManager.prototype, "isPortrait", { get: function () { return this.classifyOrientation(this.screenOrientation) === 'portrait'; } }); /** * Returns true if the screen orientation is in landscape mode. * * @name Phaser.ScaleManager#isLandscape * @property {boolean} isLandscape * @readonly */ Object.defineProperty(Phaser.ScaleManager.prototype, "isLandscape", { get: function () { return this.classifyOrientation(this.screenOrientation) === 'landscape'; } }); /** * Returns true if the game dimensions are portrait (height > width). * This is especially useful to check when using the RESIZE scale mode * but wanting to maintain game orientation on desktop browsers, * where typically the screen orientation will always be landscape regardless of the browser viewport. * * @name Phaser.ScaleManager#isGamePortrait * @property {boolean} isGamePortrait * @readonly */ Object.defineProperty(Phaser.ScaleManager.prototype, "isGamePortrait", { get: function () { return (this.height > this.width); } }); /** * Returns true if the game dimensions are landscape (width > height). * This is especially useful to check when using the RESIZE scale mode * but wanting to maintain game orientation on desktop browsers, * where typically the screen orientation will always be landscape regardless of the browser viewport. * * @name Phaser.ScaleManager#isGameLandscape * @property {boolean} isGameLandscape * @readonly */ Object.defineProperty(Phaser.ScaleManager.prototype, "isGameLandscape", { get: function () { return (this.width > this.height); } });


Slider
RangeSlider
Focus
-
Alt
+
W
Focuses the slider (clicking on it
or
tabbing also work)
-
Alt
+
Q
Focuses the range slider (clicking
on
it or tabbing also work)
Supported keys and user actions
-
Right
Increments the value by a small
step
(equivalent to Up)
-
Left
Decrements the value by a small
step
(equivalent to Down)
-
Page
Up
Increments the value by a large
step
-
Page
Down
Decrements the value by a large
step
-
Home
Move the draghandle to the min
value
-
End
Move the draghandle to the max
value
-
Tab
Tabs to second range slider
draghandle or next focusable page
element
-
Shift
+
Tab
Tabs to first range slider
draghandle
or previous focusable page element


HOME
ABOUT
CONTACT
UI/UX & Graphic Designer
I am a Graphic & Web Designer based in New York, specializing in User Interface Design and Development.


Phone Calls¶
Creating a Regular Phone Call¶
Note
Example files to look at:
tutorial_5_coffee.rpy
tutorial_11_story_call.rpy
A brief overview of the steps required (more detail below):
Create a label + the correct suffix for the phone call.
my_label_outgoing_ja
to make an outgoing call to the characterja
available.my_label_incoming_ja
to trigger an incoming call from the characterja
after the corresponding timeline item has been played.my_label_story_call_ja
to create a required Story Call after this timeline item from the characterja
.
Write the phone call dialogue.
End the phone call with
return
Phone calls that are tied to a particular timeline item follow a specific naming convention so the program can find them and make them available to the player. If you have a timeline item (i.e. a chatroom, Story Mode section, or Story Call) titled
label day_1_4:
then an incoming call should be named:
label day_1_4_incoming_z:
an outgoing call should be named:
label day_1_4_outgoing_z:
and an attached Story Call should be named:
label day_1_4_story_call_z:
where z
is the variable of the character whom the player is calling or who is calling the player (see Creating Characters for a list of the existing characters).
The following section will cover incoming and outgoing calls to characters that are optional for continuing with the story. You can find more information on Story Calls at Story Calls.
Incoming Calls¶
Once the player is done playing through a timeline item, the program will automatically make any calls associated with that timeline item (through the naming convention mentioned above) available. If the program finds an incoming call label, then when the player finishes the timeline item they will receive an incoming call from the associated character. They have ten seconds to pick the phone call up, or they can reject the call and phone the character back later.
Note
There can only be one incoming phone call after a given timeline item. However, it is possible to have both an incoming call after a chatroom as well as a different incoming call after an attached Story Mode.
If the player rejects the incoming call or lets the timer run out without answering it, it acts like an outgoing call and the player can phone the character back. The player can typically play one additional timeline item before the phone call “expires” and they cannot call the character back to receive that conversation.
Outgoing Calls¶
Once a player finishes playing through an item that has associated outgoing calls (linked through the naming conventions mentioned above), all the associated outgoing calls will be made available. The player must then click on the characters in their Contacts list or use the redial button next to a past phone call on the History tab to call the character.
An outgoing call is typically available immediately after its associated timeline item and for one additional timeline item following when it became available, after which it will “expire” and the player will be unable to view that conversation by calling the character.
Note
There can be up to one outgoing call per character available after each timeline item. If a player misses an incoming call from a character who also has an outgoing call, both conversations will be added to the pool of available calls. The player can then call the character back multiple times to receive both conversations.
Note
For development, there is an option to show an indicator underneath a character’s contact image when there is a call available for them. You can find it in the Developer options titled Available call indicator.
Missed Calls¶
If the timer on an incoming phone call runs out, the phone call will be marked as “missed” and moved to the pool of available calls. The player can then call the character back to receive that conversation.
Phone calls can also be missed if real-time mode is turned on from the Developer settings. If an incoming call is scheduled to occur after a chatroom at 10:20 and the player opens the game after a new chatroom became available at 13:30, then they will receive a notification of a missed call at 13:30 (just before the new chatroom). If not too much time has passed since the missed call, they can then phone the character back to receive that conversation.
Additionally, if the player is using the game but has not yet played the 10:20 chatroom by 13:30, then if the game is open at 13:30 they will receive an incoming call which they can either accept or reject. This acts the same way as if the incoming call had been received immediately after playing the chatroom.
You can also change the dialogue of a phone call depending on whether the player picked up when the character first called, or if they are calling the character back. For more on that, see Phone Callbacks.
Phone Call Expiry¶
As mentioned, both incoming and outgoing phone calls will become unavailable after a period of time has passed. In-game, this time is tracked via the availability of timeline items.
By default, a phone call will “expire” or become unavailable one additional timeline item after it first became available. In practice, this means if there are timeline items set up at the following trigger times:
10:20 - Chatroom
11:40 - Chatroom + Story Call (Yoosung)
13:52 - Solo Story Mode
16:00 - Chatroom + Story Mode
Say that the player can call Yoosung after the 10:20 AM chatroom. The “one timeline item” default timeout means that the player will be able to call Yoosung and get that particular outgoing call any time between 10:20 (after they’ve played the chatroom) and 13:51 (before they’ve played the 13:52 chatroom (sequential mode)/before it’s become available (real-time)).
Note
Note also that the fact that there’s a story call with Yoosung at 11:40 does not affect the availability of his outgoing phone call. Each outgoing/incoming phone call is treated independently of any other phone calls, so it’s even possible for the character to have more than one outgoing phone call “queued” at the same time.
e.g. if there’s an incoming call from Yoosung at 10:20 which the player misses, and then an outgoing call will become available after the 11:40 chatroom, then at 13:00 the player can call Yoosung once to receive the missed incoming call, and then call again to receive the outgoing call.
You can adjust the number of timeline items after which a phone call will expire through the phone_timeout_dict
found in variables_editable.rpy
. This dictionary takes the name of a phone call label and an integer (whole number) representing the number of timeline items after which this phone call should expire. The lowest valid number for this field is 0, which means that if the player doesn’t play the phone call before the timeline item immediately after it becomes available, then it will expire. So, if a phone call with an expiry of 0 becomes available at 10:20 using the above timeline, then the player must call the character before 11:40 to see that call.
define phone_timeout_dict = dict(
# TUTORIAL DAY
tutorial_chat_incoming_z=2,
tutorial_chat_outgoing_y=0,
# Feel free to add more below
)
Creating a Story Call¶
Mysterious Messenger also includes “story calls”, phone calls from the characters which the player must see before they can continue with the story. Story calls can be standalone, appearing on the timeline as their own story with a trigger time, or they can be attached to chatrooms or standalone story mode items as well and played after the main/parent timeline item is complete.
To create a standalone story call, see Story Calls. Otherwise, the program will automatically create a story call if a certain naming convention is used. If the label of the parent item (i.e. a chatroom or standalone story mode) is newyear_4_2
, then you can create a label with the suffix _story_call_
+ the file_id of the character who will be calling the player e.g.
label newyear_4_2_story_call_ja:
# This is a label for a story call from the character ja
Unlike story mode labels, there is no “generic” story call; all story calls must come from a particular character who will be calling the player.
Tip
If you want to hide the identity of the caller, you can create a special “Anonymous” ChatCharacter whose file_id you can use for the story call label.
Story calls, like chatrooms, can also expire if the player is playing in real-time or if they hang up in the middle of the call. Expired story calls are treated as though the caller left the player a voicemail. As with chatrooms, the expired version of the story call is found at the name of the original story call label + _expired
:
label newyear_4_2_story_call_ja_expired:
# The expired version of the phone call
A player who misses a story call or hangs up in the middle of it will either be able to play the expired version to continue the story, or can “buy back” the story call to experience the participated version.
Story call dialogue is written the same way as regular phone calls.
Writing a Phone Call¶
Phone call dialogue uses the regular character variables and doesn’t have any special arguments such as (img=True)
or special fonts. Just use the character variable and write out dialogue in quotations e.g.
label day_1_4_incoming_z:
z "Hi, [name]! This is phone call dialogue."
z "You write it like this."
return
Phone call labels end with return
like all other labels.
You may also find Ren’Py’s “monologue mode” helpful, since you won’t be switching between expressions or different speakers very often. See tutorial_5_coffee.rpy
for an example of this.
Providing Choices¶
Menus for phone calls are written very similarly to menus in other parts of the game:
menu:
extend ''
"I love cats.":
ju "I do as well."
"I like dogs better.":
ju "Hmm. I will respectfully disagree with your opinion."
The only notable difference is that you should include the line extend ''
right after the menu:
statement and before the first choice. This will keep the dialogue said just before the menu on-screen while the choices are visible.
This menu, like others, also takes a paraphrased
argument for either the menu as a whole or for an individual choice. For more information, see Paraphrased Choices.
Note also that if you are writing out the main character’s dialogue directly after a choice, the first line does not require a (pauseVal=0)
clause.
Changing a Character’s Voicemail¶
If the program determines there are no phone calls available for a character when the player phones them, it will automatically play the character’s voicemail instead. You can change the character’s voicemail whenever you like during a route.
To update a character’s voicemail, use:
$ ja.voicemail = "voicemail_1"
where ja
is the variable of the character whose voicemail you’re changing, and voicemail_1
is the name of the label where the voicemail can be found. The default voicemail, “voicemail_1”, can be found in 01_mysme_engine/phonecall_system.rpy
.
Voicemails are written the same way as regular phone calls, though there are no restrictions on what a voicemail label can be called (but it’s recommended you pick a descriptive name for it, probably including the word “voicemail”).
You can use a character to say voicemail dialogue, or you can use the “generic” voicemail character, vmail_phone
to say dialogue e.g.
label voicemail_1():
voice "voice files/voicemail_1.mp3"
vmail_phone "The person you have called is unavailable right now. Please leave a message at the tone or try again."
return
As shown above, you can also add voice files to the voicemail, which will be played during the next line of dialogue.
Phone Callbacks¶
Missed Calls¶
Tip
You can see an example of a missed incoming call callback if you miss Seven’s call after the ‘Inviting Guests’ chatroom at 6:11am and call him back before 9:30am (in real-time) or by letting the call time out/hanging up before answering and then phoning him back (in sequential mode). The code is near the top of tutorial_2_emails.rpy
in the tutorial_day_scripts
folder.
Sometimes the player will miss a call from a character, either from playing in real-time or by ignoring the call when it comes up. By default, when the player calls the character back, if that call is available then the player will receive the exact same conversation they would have had with the character if they had picked up the phone when they initially called.
However, you can also alter the dialogue to account for the player calling the character back. The program will look for this alternate dialogue under a special label with the suffix _incoming_z_callback
where z
is the file_id of the character whom the player is phoning back. More specifically, if you had the following chatroom and incoming call:
label day_7_chat_4():
scene morning
s "This is some dialogue for the chatroom!"
return
label day_7_chat_4_incoming_s():
s "Hello [name]!"
s "I'm glad you picked up~"
# ...
return
Then you can create the “callback” label for s
’s incoming call at:
label day_7_chat_4_incoming_s_callback():
s "[name]!!! You called me back, haha."
s "I thought maybe you were busy so I didn't wanna bother you."
s "But I'm glad you called."
# ...
return
If this callback
label is present, the program will jump to it when the player is calling the character back instead of going to the regular incoming_s
label. In replay, if the player has seen both the callback version of the call as well as the regular version, they will be prompted with a menu that allows them to pick whether they want to replay the regular version or the callback version.
If you’ll be reusing dialogue between the two phone calls, it might be useful to create a second label that both calls can jump to. For example:
label day_7_chat_4():
scene morning
s "This is some dialogue for the chatroom!"
return
label day_7_chat_4_incoming_s():
s "Hello [name]!"
s "I thought maybe you'd be having lunch and wouldn't pick up."
jump day_7_chat_4_incoming_s_ending
label day_7_chat_4_incoming_s_callback():
s "[name]!!! You called me back, haha."
s "I thought maybe you were busy so I didn't wanna bother you."
s "But I'm glad you called."
jump day_7_chat_4_incoming_s_ending
label day_7_chat_4_incoming_s_ending():
s "Are you busy right now?"
menu:
extend ''
"No, not particularly.":
s "Great! So I had this idea for a cat cafe I wanted to bring up in the chatroom."
s "Don't tell Jaehee though, okay? She might get mad."
"Yeah, I should probably get something done today.":
s "Aw... don't be like that..."
s "But, if you really have to leave, I'll understand."
s "Just call me back when you're free!"
s "Oh... looks like I actually have to go already. I'm sorry for ditching you!"
s "Let's talk again later in the chatroom. Toodles!"
return
The two calls start differently, but both jump to the day_7_chat_4_incoming_s_ending
label to finish off the phone call. This can help keep your code more organized so you’re not repeating dialogue in two separate labels.
Hanging Up¶
You can also define a callback for when the player hangs up in the middle of a phone call. First, in variables_editable.rpy
scroll down to the header PHONE HANG UP CALLBACK and find the variable default phone_hangup_callback
. By default, it is set to the example function hang_up_callback_fn
, but you can change this to whatever you like or even change it in the middle of a route.
phone_hangup_callback
should be given the name of a function which takes one parameter, the call that the player was in the middle of when they hung up the phone. The call is stored as a PhoneCall object, which has several useful fields you can access for more information on the call such as the caller and the label it’s attached to. The most relevant fields are:
- caller
The ChatCharacter object of the person the player was on the phone with.
- phone_label
A string with the name of the label where this phone call is found.
- call_status
A string that is one of “incoming”, “outgoing”, “missed”, or “voicemail”. Details the status of the phone call.
- voicemail
True if this phone call is a character’s voicemail message rather than a proper conversation. Typically if you check this variable, it’s to ignore it.
You can use as many of these fields to check for details on the phone call as you like, then use that information to narrow down what the program should do. Like the profile picture callback, the function should return the name of a label to jump to. For example, the character might try phoning the player back, or you might lower the character’s affection for the player. An example might look like:
init python:
def hang_up_callback(phonecall):
if phonecall.phone_label == 'day_7_chatroom_10_incoming_s':
return 'day_7_chatroom_10_s_hangup'
return
label day_7_chatroom_10_s_hangup():
compose text s:
s "[name], is your phone's connection okay?"
s "The call just dropped T_T"
label day_7_chatroom_10_s_hangup_menu1
return
This can be as elaborate as you want it to be, including setting conditions that allow the player to pass plot branches or acknowledging that the player hung up at a certain point. A more complex example might be:
default knows_about_bomb = False
label day_7_chatroom_10_incoming_s():
s "[name]... I know this might be hard to hear, but I should tell you now."
s "I understand if you don't want to be a part of the RFA after this."
$ knows_about_bomb = True
s "There is a bomb in the apartment. It was supposed to protect sensitive information."
# ...
return
# Presuming the same callback function is used as shown above
label day_7_chatroom_10_s_hangup():
if knows_about_bomb:
compose text s:
s "Hey... I'm sure that was a lot of information so I get why you hung up."
s "You can call me back whenever you're ready to talk."
$ create_outgoing_call('day_7_chatroom_10_about_bomb', s)
else:
compose text s:
s "[name], I didn't even get to tell you... you must be really upset."
s "I think this is only going to make you more upset, but..."
s "..."
s "Call me when you get a moment, okay?"
$ create_outgoing_call('day_7_chatroom_10_re_explain_issue', s)
return
In this way, you can create context-sensitive callbacks so that the characters remember what they talked about with the player and can react to their actions in meaningful ways.
Phone-Only Characters¶
Like with Story Mode, sometimes you may have a character who only needs to exist for a phone call but doesn’t need all the bells and whistles of a full ChatCharacter definition. There are two ways to define such characters:
Dialogue-Only Phone Characters¶
For characters that only need to speak in phone calls, but don’t need a profile picture or any other information, you can create a regular Character and inherit from the phone_char
object. This applies in situations such as:
The player can hear the voice of someone else in the background of a call (e.g. the player called Yoosung, but can hear a professor talking in the background on occasion)
The player called a regular character, but the person who picked up was not the regular character (e.g. the player phoned Jumin, but his father picked up instead)
You are defining a specific voicemail voice
These characters don’t need their own profile picture, but should have their own Character object to speak dialogue with so that the player can properly use the voice tagging system to mute characters they don’t want to listen to.
To define a dialogue-only phone character, you can do:
define hana_phone = Character("Hana", kind=phone_character)
You can then write dialogue in-game via:
hana_phone "This is some dialogue said by Hana."
hana_phone "It uses the other_voice tag."
Incoming Call Phone Characters¶
For characters who can call the player, but don’t need a fully-fledged character definition, there’s the special PhoneCharacter
class you can use to define a character for phone calls.
PhoneCharacters only need a name, a profile picture, and a file_id. Like with regular characters, the file_id is used to associate them with phone calls, so you can write incoming call labels that are automatically detected by the program. It also helps the program find the phone call to fill out the History.
A typical PhoneCharacter definition might look as follows:
default hana_phone = PhoneCharacter("Hana", "Profile Pics/Other/hana-1.webp", 'ha')
You can put this definition anywhere you like, though it’s usually a good idea to keep your character definitions in a central place where you can find them later.
Then, to write a phone call for this character, you can add a label like you would with the regular cast of characters:
label day_3_chat_7():
scene evening
y "Hi [name], sorry to bother you with this!"
y "I'm about to write a test so I gave your number to a friend."
y "She'll call you if anything changes."
y "Wish me luck!"
exit chatroom y
label day_3_chat_7_incoming_ha():
hana_phone "Hi, this is... um, [name], I think? Yoosung gave me your number."
hana_phone "Nothing's wrong, I just wanted to check if the number worked."
hana_phone "Haha, this is actually pretty awkward, so I think I'll hang up now."
hana_phone "Bye."
return
A PhoneCharacter
acts the same as a ChatCharacter
in nearly all respects, including the ability to update their profile picture, name, and more, but they won’t appear in the player’s contact list or on the home screen as a character profile. If you want that functionality, you should define them as a ChatCharacter instead (see Creating Characters).
Additionally, if the player misses a call from a PhoneCharacter
, they will only be able to call the character back if that call is still available. In practice, this means that the player can’t call a PhoneCharacter
whenever they like; only when an outstanding call is available. Outgoing calls to PhoneCharacter
are technically supported if an outgoing call is available; however, if you wish to allow for outgoing calls, you should define this character using the ChatCharacter
class instead of PhoneCharacter
, as phone-only characters do not have a contact image which makes it difficult for players to call them.


Suggested Destinations
See our top pick of cities visited by most guests
Port Harcourt
375 hotels
Ibadan
435 hotels
Calabar
285 hotels
Kaduna
334 hotels
Owerri
173 hotels
Ikeja
282 hotels
Today's Top Deals
A quick views of hotels offering the best deals
Popular cities with Hotel.ng travellers
See the top destinations people are travelling to
Select the Cities to Book Hotels in
12,540 Hotels in 1,810 cities/towns in Nigeria
297 hotels in Abia with over 155 reviews
463 hotels in Abia with over 268 reviews
296 hotels in Abia with over 32 reviews
96 hotels in Abia with over 18 reviews
0 hotels in Abia with over 3 reviews
334 hotels in Abia with over 244 reviews
266 hotels in Abia with over 45 reviews
111 hotels in Abia with over 59 reviews
627 hotels in Abia with over 579 reviews
99 hotels in Abia with over 6 reviews
1,034 hotels in Abia with over 2,202 reviews
287 hotels in Abia with over 118 reviews
24 hotels in Abia with over 6 reviews
370 hotels in Abia with over 450 reviews
132 hotels in Abia with over 108 reviews
325 hotels in Abia with over 286 reviews
717 hotels in Abia with over 459 reviews
282 hotels in Abia with over 130 reviews
11 hotels in Abia with no reviews
0 hotels in Abia with no reviews
50 hotels in Abia with over 51 reviews
372 hotels in Abia with over 325 reviews
232 hotels in Abia with over 194 reviews
305 hotels in Abia with over 289 reviews
31 hotels in Abia with over 23 reviews
3,104 hotels in Abia with over 7,894 reviews
250 hotels in Abia with over 181 reviews
496 hotels in Abia with over 533 reviews
28 hotels in Abia with over 9 reviews
69 hotels in Abia with over 45 reviews
184 hotels in Abia with over 84 reviews
616 hotels in Abia with over 407 reviews
341 hotels in Abia with over 420 reviews
33 hotels in Abia with over 4 reviews
27 hotels in Abia with over 9 reviews
283 hotels in Abia with over 56 reviews
317 hotels in Abia with over 243 reviews
20 hotels in Abia with over 48 reviews
Special Hotel Deals and Offers
Enter your email address and recieve secret hot deals
We've been featured in
Get More Bookings for Your Hotel
Find out why over 12,540 hotel managers trust Hotel.ng
Get the app
hotels.ng
Book your hotel instantly with our Andriod App.


I am someone who is very ambitious, driven, independent and creative.
Honesty is a very important thing for me. A friend of mine made a purchase on her credit card using my computer, which then autosaved her card number. We had a falling out amongst our friends and later on when I was making a purchase, a friend who happened to be at my house saw her credit card number pop up as an option in my shopping cart. She toyed with the idea of using it for our purchase as payback for the emotional damage to our friendship. I couldn’t do it at the time.
As a Maori I have learnt the importance of listening more than I speak. I think this has been a massive strength for me in my life. I’ve been able to fly under the radar and really learn so much.
The 16 personalities test tells my I am ENTP, like Tyrion Lannister. My strengths include my keen intellect and wit, my creativity, my ability to look at a situation from all angles possible. My limitations includes the fact that my moral compass doesn’t seem to consider other people’s feelings (Although I think I’m quite compassionate), and that I sometimes pursue things out of mere boredom and not because I am invested in any particular cause.
Whenever my WoF and rego needs to be renewed, I once again have a serious dilemma in having to decide whether I should pay to get it renewed or leave it in the hands of the gods. I weighed up the decision by thinking about karma and knowing that the very day my rego clocks out, I'll probably get pulled over at a cop stop.
My strengths mean I am a quick learner and I can power through any task. My limitation is definitely follow-through over a long period of time. The reason I took this course instead of a university degree is because I was worried that if it were more than a year, I would not keep the fire burning.
My limitation is that because I am so creative and such a powerhouse, my attention span is short and I can become bored quite easily. This means that my effort is more like spurts of energy that can putter out over a long period of time.
I had a group presentation for a PR class at uni. We had students of all levels including international students whose english was quite poor. The tension wasn’t so much of a tension as it was an inability to communicate. We just went over their heads and did their portion of the work for them just to be able to hand in an assignment of quality. We got the grade we wanted, but admittedly this was not the best approach. If the situation were to be repeated, I would take more collaborative approach to the assignment.


The new view engine is being released by Microsoft and looking at the announcement I feel a bit confused. Two questions flying in my head:
For me this one would probably be enough to jump onto that with head. I still have the question on StackOverflow about testing the view hanging around. It looks like now I have the answer.
Next, the code blocks mixed together with HTML:
Currently it looks a bit weird to me. We need to get used to it.
So you can do lots of interesting things using such syntax. Additionally I have to state that the engine has a special tag - text. It is used actually to avoid adding any wrapping tag to markup serving the purpose of grouping things together.
Currently I create a whole bunch of HTML helpers (written in .cs file in C#) which look very ugly should you have more than just a couple of them.
It just becomes messy, hard to change, support and maintain. And the fact that C# generates HTML is very annoying for the same reasons. That should be the view's concern.
Now we have it - just create a file, mark its content as helper and it is available on all the pages. Beauty!
(NOTE: actually after a bit more thinking we can do similar in Spark or NHaml. The word similar is used intentionally).
which would be equivalent for
But I have no doubt that this engine is the way better than WebForms. Can't yet say it is better than NHaml and Spark (and would not be fair to say that as they solve a bit different problems).
This time I feel that Microsoft has listened to the developers and produced something that really brings value, productivity, less frustration and more fun (and fun is the starting point of being a happy developer who can bring value to a company).
- Why not take existing one and just help to make it better/sponsor?
- Is it going to be a new productive thing or will it just make the view to be more spaghetti-like?
Unit Testable: The new view engine implementation will support the ability to unit test views (without requiring a controller or web-server, and can be hosted in any unit test project – no special app-domain required).
For me this one would probably be enough to jump onto that with head. I still have the question on StackOverflow about testing the view hanging around. It looks like now I have the answer.
the start of a code block with Razor using a @ character. Unlike <% %> code nuggets, Razor does not require you to explicitly close the code-blockThis is a huge typing and "mistake" saver. I like it, but still have the concern about the readability and ambiguity of such syntax. I need to give it a try first to tell whether this helps to do the job or screws things up. But the intention is definitely good.
Next, the code blocks mixed together with HTML:

@foreach(var p in products) {
<EMAIL_ADDRESS>($@p.Price)

}

Currently it looks a bit weird to me. We need to get used to it.
So you can do lots of interesting things using such syntax. Additionally I have to state that the engine has a special tag - text. It is used actually to avoid adding any wrapping tag to markup serving the purpose of grouping things together.
By default content emitted using a @ block is automatically HTML encoded to better protect against XSS attack scenarios.That is something that I would expect from any view engine - encode by default. So far so good.
create re-usable HTML helpers using a more declarative approach.I like this part a lot (and I mean it), I won't repeat what it is said in the post, but will explain how I can benefit from it.
Currently I create a whole bunch of HTML helpers (written in .cs file in C#) which look very ugly should you have more than just a couple of them.
It just becomes messy, hard to change, support and maintain. And the fact that C# generates HTML is very annoying for the same reasons. That should be the view's concern.
Now we have it - just create a file, mark its content as helper and it is available on all the pages. Beauty!
(NOTE: actually after a bit more thinking we can do similar in Spark or NHaml. The word similar is used intentionally).
One other useful (and extremely powerful) feature we are enabling with Razor is the ability to pass “inline template” parameters to helper methods. These "inline templates" can contain both HTML and code, and can be invoked on-demand by helper methods.Previously we, generally, injected C# code into the template. Now we can do the other way around. Absolut power. Only God knows where it can bring us :)
What I want from it
One of the most frustrating things working with views in ASP.NET MVC is passing the values from a controller to a view. So the syntax that would read values from ViewData dictionary would the biggest time saver. Something along this:
@currentTime.ToString("d")
which would be equivalent for
((DateTime)ViewData["currentTime"]).ToString("d")
So that we do not even need to provide the time of the object in the ViewData.Summary
The first impression is very positive. Though I have some doubts in the syntax acceptance.But I have no doubt that this engine is the way better than WebForms. Can't yet say it is better than NHaml and Spark (and would not be fair to say that as they solve a bit different problems).
This time I feel that Microsoft has listened to the developers and produced something that really brings value, productivity, less frustration and more fun (and fun is the starting point of being a happy developer who can bring value to a company).
With the Visual Studio support, does it get highlighted in such a fashion that it makes the syntax readable and unambiguous? (I haven't installed Razor to take a look myself yet)


Toggle navigation
Menu
Home
Bucketlist
Rishikesh
Bungy Jump | 83 mts | Highest in India
Caption
Quote
Detail
TimeStamp


AMAZING PEOPLE ARE MAKING AMAZING DESIGNS IN A FUN ENVIRONMENT
When you work with us,you are part of a team of commited collaborators.All aspects of our software development process are intergrated,interactive
and agile
Our approach unifies design,development and product management to create exceptional products.
We start each project by understanding your business goals,the user's needs and the project requirements
Then we help translate them into effective and elegant solutions
Before we sign a contract or write a line of code our team will spend the necessary time needed to understand the product vision
The outcome of this scoping session is a proposal or high-level statement of work
Based on your requirement,we can provide a complete offering or just the pieces you need
Our design practice offers a full range of services including brand strategy, interaction and visual design and user experience testing.
Throughout your project, our designers create and implement visual design and workflows, solicit user feedback and work with you to make sure what gets built is what is needed.
All engineers are fluent in the latest enterprise, mobile and web development technologies.
They collaborate with your team to write, and improve code on a daily basis, using proven practices such as test-driven development and pair programming.
Planning and development is iterative. Because we are constantly coding and testing, the products we build are always ready to go live.
This iterative process allows for changes as business requirements evolve.


Buttons
Grids
Button Example
Button with default icon
Grid cell 1/3 of width
Grid cell 2/3 of width
Grid cell 1/5 of width
Grid cell 4/5 of width