Permalink
Browse files

Implements game iframes.

This change adds view.game-init.js, which listens to button clicks that
are meant to launch a game.  This change also adds all DOM hooks for the
game sandbox, as well as input proxying and game exiting.

Squashed commit of the following:

commit dfb660cb42568f20b0158c1adc86a3d27ac3a9f5
Author: Jeremy Kahn <jeremyckahn@gmail.com>
Date:   Wed Aug 15 17:56:17 2012 -0500

    Changes menu button focus so it does not reset if a user switches back quickly.

commit 52a8b6c065bdf07b53e32a77469c29128c8681ff
Author: Jeremy Kahn <jeremyckahn@gmail.com>
Date:   Wed Aug 15 17:29:54 2012 -0500

    Prevents spurious webkitTransitionEnd events.

commit 481b67a08d595731a625566fc3e5b62042c40fd0
Author: Jeremy Kahn <jeremyckahn@gmail.com>
Date:   Wed Aug 15 17:24:16 2012 -0500

    Disables the tab button for menu buttons.

commit 30c319e91dd8c7401b4b302fa20ce077bd4c986d
Author: Jeremy Kahn <jeremyckahn@gmail.com>
Date:   Wed Aug 15 17:15:41 2012 -0500

    Prevents user from tabbing off of the last button in a menu.

commit 9483458f360341496913ee17f7e99e255916fe79
Author: Jeremy Kahn <jeremyckahn@gmail.com>
Date:   Wed Aug 15 17:09:09 2012 -0500

    Fixes some focus issues after exiting a game.

commit 9a2784f5e829759c80a04a6f9ca7593f14f27a1e
Author: Jeremy Kahn <jeremyckahn@gmail.com>
Date:   Wed Aug 15 16:40:48 2012 -0500

    Adds nice fade transitions to the iframes.

commit cd0709dcfd4e9ea5716a733dd919c7394a636de7
Author: Jeremy Kahn <jeremyckahn@gmail.com>
Date:   Wed Aug 15 16:08:12 2012 -0500

    Implements game input proxying and game quit functionality.

commit 05bc819e12b31a7c47180014f6c8d13afd52e7e9
Author: Jeremy Kahn <jeremyckahn@gmail.com>
Date:   Wed Aug 15 15:34:40 2012 -0500

    Adds functionality to launch a full-page iframe.

commit 9865d33559ac4671d66b67f9be722d2c0b9d5d51
Author: Jeremy Kahn <jeremyckahn@gmail.com>
Date:   Wed Aug 15 15:15:37 2012 -0500

    Gets DOM ready for game-init.

commit faf72901afb9b0410e27dc768123b27138acd0e3
Author: Jeremy Kahn <jeremyckahn@gmail.com>
Date:   Mon Aug 13 11:22:04 2012 -0500

    Implements functionality to dynamically build and store an iFrame to GameInit.

commit e864405d22f3fa15fa7dc595b496f84490f6009d
Author: Jeremy Kahn <jeremyckahn@gmail.com>
Date:   Mon Aug 13 11:08:46 2012 -0500

    Adds bootstrap code for GameInit module.
  • Loading branch information...
1 parent af9f4c8 commit e34b8d3eb12a8093fc2c7ff14ba83cdb6e5737f7 @jeremyckahn committed Aug 16, 2012
Showing with 248 additions and 24 deletions.
  1. +45 −1 ui/css/pine.css
  2. +17 −0 ui/iframe-test.html
  3. +15 −4 ui/index.html
  4. +1 −0 ui/js/app.js
  5. +2 −0 ui/js/constants.js
  6. +14 −2 ui/js/init.js
  7. +109 −0 ui/js/view/view.game-init.js
  8. +21 −11 ui/js/view/view.menu-pager.js
  9. +24 −6 ui/js/view/view.menu.js
View
@@ -7,10 +7,54 @@ ul {
margin: 0;
}
-.pine-container {
+#pine-container {
padding-top: 30px;
}
+/** GAME CONTAINER STYLES START */
+#game-container {
+ visibility: hidden;
+ position: fixed;
+ height: 100%;
+ left: -9999em;
+ opacity: 0;
+ top: 0;
+ width: 100%;
+ -webkit-transition: opacity;
+ -webkit-transition-duration: 400ms;
+}
+
+#game-container.playing {
+ visibility: visible;
+ left: 0;
+ opacity: 1;
+}
+
+/* This must come after #game-container.playing */
+#game-container.exiting {
+ opacity: 0;
+}
+
+ #game-container .input-proxy {
+ height: inherit;
+ position: absolute;
+ left: 0;
+ top: 0;
+ width: inherit;
+ }
+
+ #game-container .input-proxy .key-proxy {
+ position: absolute;
+ left: -9999em;
+ }
+
+ #game-container iframe {
+ border: none;
+ height: inherit;
+ width: inherit;
+ }
+/** GAME CONTAINER STYLES END */
+
/** MENU STYLES START */
.main-menu {
margin: 40px auto 0;
View
@@ -0,0 +1,17 @@
+<!DOCTYPE html>
+<html lang="en">
+ <head>
+ <meta charset="utf-8">
+ <title>Pine</title>
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
+ <meta name="description" content="">
+ <meta name="author" content="">
+ <link href="css/bootstrap.css" rel="stylesheet">
+ <link href="css/pine.css" rel="stylesheet">
+ <link href="css/pine-animations.css" rel="stylesheet">
+ </head>
+ <body>
+ <div class="pine-container">
+ <h1 class="welcome">I am a test iframe! Hello there!</h1>
+ </body>
+</html>
View
@@ -11,15 +11,15 @@
<link href="css/pine-animations.css" rel="stylesheet">
</head>
<body>
- <div class="pine-container">
+ <div id="pine-container">
<h1 class="welcome">Hello. Welcome to Pine.</h1>
<div id="main-menus" class="menu-pager">
<div class="menu-pager-rail">
<div class="menu main-menu">
<ul class="main-menu-btn-array menu-btn-array">
<li>
- <button class="btn main-menu-btn">Item 1</button>
+ <button class="btn main-menu-btn game-init" data-game-url="./iframe-test.html">iframe test</button>
</li>
<li>
<button class="btn main-menu-btn">Item 2</button>
@@ -74,7 +74,13 @@ <h1 class="welcome">Hello. Welcome to Pine.</h1>
</div>
</div>
- </div> <!-- /pine-container -->
+ </div> <!-- /main-menus -->
+ <div id="game-container">
+ <div class="input-proxy">
+ <input class="key-proxy">
+ </div>
+ <!-- Gets filled in dynamically. -->
+ </div>
<div id="debug-footer" class="debug">
<p id="debug-footer-trigger">Mouse over to see debug controls.</p>
<div class="debug-modal">
@@ -85,8 +91,13 @@ <h2>Keyboard navigation</h2>
<dt>W/E</dt>
<dd>Change menu pages</dd>
</dl>
+ <h2>In-game navigation</h2>
+ <dl>
+ <dt>Esc</dt>
+ <dd>Quit the game</dd>
+ </dl>
</div>
- </div>
+ </div> <!-- /debug-footer -->
<script src="js/lib/jquery.js"></script>
<script src="js/lib/bootstrap.js"></script>
<script src="js/lib/underscore.js"></script>
View
@@ -13,6 +13,7 @@ define(['js/constants'], function (constants) {
'constants': constants
,'view': {
'mainMenuPager': null
+ ,'gameInit': null
}
,'util': {}
};
View
@@ -17,6 +17,8 @@ define(['exports'], function (constants) {
,'UP': 38
,'RIGHT': 39
,'DOWN': 40
+ ,'ESC': 27
+ ,'TAB': 9
,'Q': 81
,'W': 87
,'E': 69
View
@@ -11,12 +11,15 @@ require([
,'js/view/view.menu'
,'js/view/view.menu-pager'
+ ,'js/view/view.game-init'
], function (
app
+
,menu
- ,menuPager) {
+ ,menuPager
+ ,gameInit) {
'use strict';
@@ -31,12 +34,21 @@ require([
}));
});
+ var $mainMenus = $('#main-menus');
app.view.mainMenuPager = new menuPager.view({
'app': app
- ,'$el': $('#main-menus')
+ ,'$el': $mainMenus
,'menuViews': menuViews
});
+ var $pineContainer = $('#pine-container');
+ var $gameContainer = $('#game-container');
+ app.view.gameInit = new gameInit.view({
+ 'app': app
+ ,'$el': $pineContainer
+ ,'$gameContainer': $gameContainer
+ });
+
console.log(app);
$(function () {
@@ -0,0 +1,109 @@
+/*jslint nomen: true, plusplus: true, undef: true, todo: true, white: true,
+ browser: true, indent: 2, maxlen: 80 */
+/*global Backbone: false, _: false, $: false, KeyRouter: false, publish: false,
+ subscribe: false */
+
+// Pine game initialization module
+define(['exports'], function (gameInit) {
+
+ 'use strict';
+
+ var $win = $(window);
+
+
+ // PRIVATE UTILITY FUNCTIONS
+
+
+ /**
+ * @param {string} src
+ * @param {jQuery} An iframe.
+ */
+ function buildiFrame (src) {
+ var $iframe = $(document.createElement('iframe'));
+ $iframe.attr('src', src);
+
+ return $iframe;
+ }
+
+
+ gameInit.view = Backbone.View.extend({
+
+ 'events': {
+ 'click .game-init': 'onClickGameInit'
+ ,'click #game-container.playing .input-proxy': 'onClickInputProxy'
+ ,'keydown #game-container.playing .key-proxy': 'onProxiedKeydown'
+ }
+
+
+ /**
+ * @param {Object} opts
+ * @param {Object} app
+ * @param {jQuery} $el
+ */
+ ,'initialize': function (opts) {
+ _.extend(this, opts);
+
+ /** @type {jQuery) */
+ this._$gameContainer = this.$gameContainer;
+
+ /** @type {jQuery) */
+ this._$inputProxy = this._$gameContainer.find('.input-proxy');
+
+ /** @type {jQuery) */
+ this._$keyProxy = this._$inputProxy.find('.key-proxy');
+
+ /** @type {jQuery} */
+ this._$iframe = null;
+
+ // This was made private, remove the public reference.
+ delete this.$gameContainer;
+ }
+
+
+ /**
+ * @param {jQuery.Event} evt
+ */
+ ,'onClickGameInit': function (evt) {
+ var $target = $(evt.target);
+ this._$iframe = buildiFrame($target.data('game-url'));
+ this._$gameContainer
+ .prepend(this._$iframe)
+ .addClass('playing');
+ this._$keyProxy.focus();
+ }
+
+
+ /**
+ * @param {jQuery.Event} evt
+ */
+ ,'onClickInputProxy': function (evt) {
+ this._$keyProxy.focus();
+ }
+
+
+ /**
+ * @param {jQuery.Event} evt
+ */
+ ,'onProxiedKeydown': function (evt) {
+ var which = evt.which;
+ if (which === this.app.constants.key.ESC) {
+ this.exitGame();
+ }
+ }
+
+
+ ,'exitGame': function () {
+ this._$gameContainer
+ .addClass('exiting')
+ // The `exiting` class triggers a webkitAnimation, `one` effectively
+ // acts as a callback.
+ .one('webkitTransitionEnd', _.bind(function (evt) {
+ this._$gameContainer.removeClass('playing exiting');
+ this._$iframe.remove();
+ this.app.view.mainMenuPager.reactivateCurrentMenu();
+ }, this));
+ }
+
+ });
+
+});
@@ -10,10 +10,6 @@ define(['exports'], function (menuPager) {
var $win = $(window);
-
- // PRIVATE UTILITY FUNCTIONS
-
-
menuPager.view = Backbone.View.extend({
'events': {
@@ -84,15 +80,29 @@ define(['exports'], function (menuPager) {
var targetMenu = this.menuViews[index];
this.$el.height(targetMenu.$el.outerHeight(true));
this._currentMenuIndex = index;
- this.app.util.keyRouter.resetHandlers();
- this._$rail.css('left', -parseInt(targetMenu.$el.css('left'), 10));
+ // The logic gets a little tricky here because an animation may not be
+ // occurring - meaning that the element is moving to the position that it
+ // is already in.
+ var targetLeft = -parseInt(targetMenu.$el.css('left'), 10);
+ if (targetLeft === parseInt(this._$rail.css('left'), 10)) {
+ // No animation occurred, just execute the callback.
+ targetMenu.activate();
+ } else {
+ this._$rail
+ .css('left', targetLeft)
+ .off('webkitTransitionEnd')
+ // Need to use $.fn.one here because multiple webkitTransitionEnd
+ // events fire on the _$rail element.
+ // TODO: Figure out why that happens...
+ .one('webkitTransitionEnd',
+ _.bind(targetMenu.activate, targetMenu));
+ }
+ }
+
- // Need to use $.fn.one here because multiple webkitTransitionEnd events
- // fire on the _$rail element.
- // TODO: Figure out why that happens...
- this._$rail.one('webkitTransitionEnd',
- _.bind(targetMenu.activate, targetMenu));
+ ,'reactivateCurrentMenu': function () {
+ this.activateMenu(this._currentMenuIndex);
}
});
View
@@ -115,6 +115,7 @@ define(['exports'], function (menu) {
'events': {
'focus button': 'onButtonFocus'
,'blur button': 'onButtonBlur'
+ ,'keydown button': 'onButtonKeydown'
}
@@ -160,6 +161,8 @@ define(['exports'], function (menu) {
,'onMenuItemSelected': function (menuItem) {
if (menuItem === this.$el && !this._isSelected) {
this.activate();
+ } else {
+ this._isSelected = false;
}
}
@@ -180,13 +183,28 @@ define(['exports'], function (menu) {
}
- ,'activate': function () {
- this._isSelected = true;
- this.$el.find('button:first').focus();
- this.app.util.keyRouter.route(
- KeyRouter.KEYDOWN, _.bind(this.onKeydown, this));
+ /**
+ * @param {jQuery.Event} evt
+ */
+ ,'onButtonKeydown': function (evt) {
+ if (evt.which === this.app.constants.key.TAB) {
+ evt.preventDefault();
+ }
+ }
+
- publish(this.app.constants.message.MENU_SELECTED, [this.$el]);
+ ,'activate': function () {
+ // TODO: This is an inefficient check, but _isSelected wasn't working.
+ // Try to fix this.
+ if (this.$el.find('button.selected').length === 0) {
+ this._isSelected = true;
+ this.$el.find('button:first').focus();
+ this.app.util.keyRouter.resetHandlers();
+ this.app.util.keyRouter.route(
+ KeyRouter.KEYDOWN, _.bind(this.onKeydown, this));
+
+ publish(this.app.constants.message.MENU_SELECTED, [this.$el]);
+ }
}

0 comments on commit e34b8d3

Please sign in to comment.