1 /* 2 * Copyright © 2009 Apple Inc. All rights reserved. 3 */ 4 5 /** 6 * @class 7 * @name TKNavigationControllerDelegate 8 * @since TuneKit 1.0 9 */ 10 11 /** 12 * Indicates that a new controller is becoming the top controller and is about to become visible on screen. 13 * 14 * @name navigationControllerWillShowController 15 * @function 16 * 17 * @param {TKNavigationController} navigationController The navigation controller. 18 * @param {TKController} controller The controller that is about to be shown by the navigation controller. 19 * @memberOf TKNavigationControllerDelegate.prototype 20 */ 21 const TKNavigationControllerWillShowController = 'navigationControllerWillShowController'; 22 /** 23 * Indicates that a new controller has become the top controller and is fully visible on screen. 24 * 25 * @name navigationControllerDidShowController 26 * @function 27 * 28 * @param {TKNavigationController} navigationController The navigation controller. 29 * @param {TKController} controller The controller that is has been shown by the navigation controller. 30 * @memberOf TKNavigationControllerDelegate.prototype 31 */ 32 const TKNavigationControllerDidShowController = 'navigationControllerDidShowController'; 33 34 TKNavigationController.inherits = TKController; 35 TKNavigationController.synthetizes = ['topController']; 36 37 /** 38 * @property {TKNavigationController} sharedNavigation The shared instance of the navigation controller. TuneKit automatically creates a single instance 39 * of the {@link TKNavigationController} class as needed, and developers should never have to create an instance themselves, instead using this property 40 * to retrieve the shared instance. 41 */ 42 TKNavigationController.sharedNavigation = null; 43 44 /** 45 * @class 46 * 47 * <p>The spatial navigation manager is a pre-insantiated singleton controller that handles the logic for all navigation between controllers. The currently 48 * showing controller is the {@link #topController}, and is the last item of the {@link #controllers} array, which traces the complete navigation history 49 * from the home controller onwards. By implementing the {@link TKNavigationControllerDelegate} protocol, the navigation controller's {@link #delegate} can 50 * track as the user navigates between controllers. While the {@link TKController#navigatesTo} property should be sufficient to alloe developers to specify 51 * what action triggers a navigation to a given controller, the {@link #pushController} and {@link #popController} methods also allow a programmatic 52 * interaction with the navigation controller.</p> 53 * 54 * @extends TKController 55 * @since TuneKit 1.0 56 * 57 * @param {Object} data A hash of properties to use as this object is initialized. 58 */ 59 function TKNavigationController (data) { 60 /** 61 * The list of controllers in the navigation stack. The controller at the first index is the root-most controller, while the controller at the last index is 62 * the top controller. 63 * @type Array 64 */ 65 this.controllers = []; 66 /** 67 * The delegate for this navigation controller, an object implementing the {@link TKNavigationControllerDelegate} protocol. 68 * @type Object 69 */ 70 this.delegate = data.delegate || null; 71 this.rootController = data.rootController || null; 72 this.previousController = null; 73 // 74 this.busy = false; 75 // 76 this.callSuper(data); 77 // see if we have a stack to restore 78 if (bookletController.archive !== undefined) { 79 var stack = bookletController.archive.navigationStack; 80 var restored_controllers = []; 81 for (var i = 0; i < stack.length; i++) { 82 var controller = TKController.controllers[stack[i]]; 83 restored_controllers.push(controller); 84 controller.loadView(); 85 } 86 // push the last controller 87 this.pushController(restored_controllers[restored_controllers.length - 1]); 88 // and fake the controllers stack 89 this.controllers = restored_controllers; 90 } 91 // see if we have a root set up 92 else if (this.rootController !== null) { 93 this.pushController(this.rootController); 94 } 95 // 96 TKNavigationController.sharedNavigation = this; 97 }; 98 99 /* ==================== Controllers ==================== */ 100 101 /** 102 * @name TKNavigationController.prototype 103 * @property {TKController} topController The controller at the top of the navigation stack of {@link #controllers}. 104 */ 105 TKNavigationController.prototype.getTopController = function () { 106 return (this.controllers.length > 0) ? this.controllers[this.controllers.length - 1] : null; 107 }; 108 109 /** 110 * Pushes a new controller to the top of the navigation stack, triggering an animated transition of the new top controller using its 111 * {@link TKController.becomesActiveTransition} property and the {@link TKController.becomesInactiveTransition} property of the previous {@link #topController} 112 * @param {TKController} controller The controller to push onto the navigation stack. 113 */ 114 TKNavigationController.prototype.pushController = function (controller) { 115 // do nothing if we're busy 116 if (this.busy) { 117 return; 118 } 119 // 120 TKTransaction.begin(); 121 // get pointers to object we'll manipulate 122 var previous_controller = this.topController; 123 var next_view = controller.view; 124 // fire delegate saying we're moving to a new controller 125 if (TKUtils.objectHasMethod(this.delegate, TKNavigationControllerWillShowController)) { 126 this.delegate[TKNavigationControllerWillShowController](this, controller); 127 } 128 // put the controller in our array 129 this.controllers.push(controller); 130 // notify of upcoming change 131 if (previous_controller !== null) { 132 previous_controller._viewWillDisappear(); 133 previous_controller.viewWillDisappear(); 134 } 135 controller._viewWillAppear(); 136 controller.viewWillAppear(); 137 // add it to the tree 138 this.view.appendChild(controller.view); 139 // 140 if (previous_controller !== null) { 141 this.transitionToController(previous_controller, controller); 142 } 143 else { 144 this.busy = true; 145 this.transitionDidComplete(); 146 TKSpatialNavigationManager.sharedManager.managedController = controller; 147 } 148 // 149 TKTransaction.commit(); 150 }; 151 152 /** 153 * Pops the top {@link #topController} off the navigation stack, triggering an animated transition of the new top controller using its 154 * {@link TKController.becomesActiveTransition} property and the {@link TKController.becomesInactiveTransition} property of the previous {@link #topController} 155 */ 156 TKNavigationController.prototype.popController = function () { 157 // do nothing if we're busy or if there's nothing to pop 158 if (this.busy || this.controllers.length < 2) { 159 return; 160 } 161 TKTransaction.begin(); 162 // fire delegate saying we're moving to a new controller 163 if (TKUtils.objectHasMethod(this.delegate, TKNavigationControllerWillShowController)) { 164 this.delegate[TKNavigationControllerWillShowController](this, this.controllers[this.controllers.length - 2]); 165 } 166 // update stack 167 var previous_controller = this.controllers.pop(); 168 var top_controller = this.topController; 169 // notify of upcoming change 170 previous_controller._viewWillDisappear(); 171 previous_controller.viewWillDisappear(); 172 top_controller._viewWillAppear(); 173 top_controller.viewWillAppear(); 174 // add it to the tree 175 this.view.appendChild(top_controller.view); 176 // transition 177 this.transitionToController(previous_controller, top_controller); 178 // 179 TKTransaction.commit(); 180 }; 181 182 /* ==================== Transition ==================== */ 183 184 TKNavigationController.prototype.transitionToController = function (previous_controller, top_controller) { 185 // mark that a transition is now in progress 186 this.busy = true; 187 // record some parameters that we will need at the end of the transition 188 this.previousController = previous_controller; 189 // figure out transitions 190 if (previous_controller !== null) { 191 if (IS_APPLE_TV && !previous_controller.enforcesCustomTransitions) { 192 previous_controller.becomesInactiveTransition = TKViewTransitionDissolveOut; 193 } 194 previous_controller.view.applyTransition(previous_controller.becomesInactiveTransition, false); 195 } 196 if (IS_APPLE_TV && !top_controller.enforcesCustomTransitions) { 197 top_controller.becomesActiveTransition = TKViewTransitionDissolveIn; 198 } 199 var top_controller_transition = top_controller.becomesActiveTransition; 200 top_controller_transition.delegate = this; 201 top_controller.view.applyTransition(top_controller_transition, false); 202 // 203 TKSpatialNavigationManager.sharedManager.managedController = top_controller; 204 // track that we're moving from screen to screen 205 window.dispatchEvent(TKUtils.createEvent('cursorwait', null)); 206 }; 207 208 TKNavigationController.prototype.transitionDidComplete = function (transition) { 209 if (!this.busy) { 210 return; 211 } 212 var top_controller = this.topController; 213 // remove the old screen 214 if (this.previousController !== null) { 215 this.view.removeChild(this.previousController.view); 216 this.previousController._viewDidDisappear(); 217 this.previousController.viewDidDisappear(); 218 } 219 // notify of completed change 220 top_controller._viewDidAppear(); 221 top_controller.viewDidAppear(); 222 // fire delegate saying we've moved to a new controller 223 if (TKUtils.objectHasMethod(this.delegate, TKNavigationControllerDidShowController)) { 224 this.delegate[TKNavigationControllerDidShowController](this, top_controller); 225 } 226 // 227 this.busy = false; 228 // pre-load screens that we can navigate to from here 229 // for (var i = 0; i < top_controller.navigatesTo.length; i++) { 230 // var navigation_data = top_controller.navigatesTo[i]; 231 // // pointer to the controller 232 // var controller = navigation_data.controller; 233 // // if it's a string, try to find it in the controllers hash 234 // if (TKUtils.objectIsString(controller)) { 235 // controller = TKController.controllers[controller]; 236 // } 237 // // skip if we have an undefined object 238 // if (controller === undefined) { 239 // continue; 240 // } 241 // // otherwise, load it if it's not been loaded before 242 // if (controller._view === null) { 243 // controller.loadView(); 244 // } 245 // } 246 // done moving from screen to screen 247 window.dispatchEvent(TKUtils.createEvent('cursornormal', null)); 248 }; 249 250 TKClass(TKNavigationController); 251