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