95f5ab7e2a84fd23ae3b9c6eaf97a631f16a596a
[platform/core/csapi/tizenfx.git] / src / Tizen.NUI / src / public / XamlBinding / NavigationPage.cs
1 using System;
2 using System.Collections.Generic;
3 using System.Collections.ObjectModel;
4 using System.ComponentModel;
5 using System.Linq;
6 using System.Threading.Tasks;
7 using Tizen.NUI.Internals;
8 using Tizen.NUI.Binding;
9
10 namespace Tizen.NUI
11 {
12     /// <summary>
13     /// A Page that manages the navigation and user-experience of a stack of other pages.
14     /// </summary>
15     // [RenderWith(typeof(_NavigationPageRenderer))]
16     [EditorBrowsable(EditorBrowsableState.Never)]
17     public class NavigationPage : Page, IPageContainer<Page>, INavigationPageController, IElementConfiguration<NavigationPage>
18         {
19         /// <summary>
20         /// Identifies the property associated with the title of the back button.
21         /// </summary>
22                 public static readonly BindableProperty BackButtonTitleProperty = BindableProperty.CreateAttached("BackButtonTitle", typeof(string), typeof(Page), null);
23
24         /// <summary>
25         /// Backing store for the HasNavigationBar property.
26         /// </summary>
27                 public static readonly BindableProperty HasNavigationBarProperty = BindableProperty.CreateAttached("HasNavigationBar", typeof(bool), typeof(Page), true);
28
29         /// <summary>
30         /// Backing store for the HasBackButton property.
31         /// </summary>
32                 public static readonly BindableProperty HasBackButtonProperty = BindableProperty.CreateAttached("HasBackButton", typeof(bool), typeof(NavigationPage), true);
33
34         /// <summary>
35         /// Identifies the Tint bindable property.
36         /// </summary>
37                 [Obsolete("TintProperty is obsolete as of version 1.2.0. Please use BarBackgroundColorProperty and BarTextColorProperty to change NavigationPage bar color properties.")] 
38                 public static readonly BindableProperty TintProperty = BindableProperty.Create("Tint", typeof(Color), typeof(NavigationPage), /*Color.Default*/Color.Black);
39
40         /// <summary>
41         /// Identifies the property associated with the color of the NavigationPage's bar background color.
42         /// </summary>
43                 public static readonly BindableProperty BarBackgroundColorProperty = BindableProperty.Create("BarBackgroundColor", typeof(Color), typeof(NavigationPage), /*Color.Default*/Color.Black);
44
45         /// <summary>
46         /// Identifies the property associated with the color of the NavigationPage's bar text color.
47         /// </summary>
48                 public static readonly BindableProperty BarTextColorProperty = BindableProperty.Create("BarTextColor", typeof(Color), typeof(NavigationPage), /*Color.Default*/Color.Black);
49
50         /// <summary>
51         /// Indicates the NavigationPage.SetTitleIcon/NavigationPage.GetTitleIcon property.
52         /// </summary>
53                 public static readonly BindableProperty TitleIconProperty = BindableProperty.CreateAttached("TitleIcon", typeof(FileImageSource), typeof(NavigationPage), default(FileImageSource));
54
55                 static readonly BindablePropertyKey CurrentPagePropertyKey = BindableProperty.CreateReadOnly("CurrentPage", typeof(Page), typeof(NavigationPage), null);
56
57         /// <summary>
58         /// Identifies the property associated with NavigationPage.CurrentPage
59         /// </summary>
60         public static readonly BindableProperty CurrentPageProperty = CurrentPagePropertyKey.BindableProperty;
61
62                 static readonly BindablePropertyKey RootPagePropertyKey = BindableProperty.CreateReadOnly(nameof(RootPage), typeof(Page), typeof(NavigationPage), null);
63         /// <summary>
64         /// Identifies the property associated with NavigationPage.RootPage
65         /// </summary>
66         public static readonly BindableProperty RootPageProperty = RootPagePropertyKey.BindableProperty;
67
68         /// <summary>
69         /// Initializes a new NavigationPage object.
70         /// </summary>
71                 public NavigationPage()
72                 {
73                         _platformConfigurationRegistry = new Lazy<PlatformConfigurationRegistry<NavigationPage>>(() => new PlatformConfigurationRegistry<NavigationPage>(this));
74
75                         Navigation = new NavigationImpl(this);
76                 }
77
78         /// <summary>
79         /// Creates a new NavigationPage element with root as its root element.
80         /// </summary>
81         /// <param name="root">The root page.</param>
82                 public NavigationPage(Page root) : this()
83                 {
84                         PushPage(root);
85                 }
86
87         /// <summary>
88         /// Gets or sets the background color for the bar at the top of the NavigationPage.
89         /// </summary>
90                 public Color BarBackgroundColor
91                 {
92                         get { return (Color)GetValue(BarBackgroundColorProperty); }
93                         set { SetValue(BarBackgroundColorProperty, value); }
94                 }
95
96         /// <summary>
97         /// Gets or sets the text that appears on the bar at the top of the NavigationPage.
98         /// </summary>
99                 public Color BarTextColor
100                 {
101                         get { return (Color)GetValue(BarTextColorProperty); }
102                         set { SetValue(BarTextColorProperty, value); }
103                 }
104
105         /// <summary>
106         /// The color to be used as the Tint of the NavigationPage.
107         /// </summary>
108                 [Obsolete("Tint is obsolete as of version 1.2.0. Please use BarBackgroundColor and BarTextColor to change NavigationPage bar color properties.")]
109                 public Color Tint
110                 {
111                         get { return (Color)GetValue(TintProperty); }
112                         set { SetValue(TintProperty, value); }
113                 }
114
115                 internal Task CurrentNavigationTask { get; set; }
116
117         /// <summary>
118         /// For internal use
119         /// </summary>
120         /// <param name="depth">The depth</param>
121         /// <returns>The page instance</returns>
122                 [EditorBrowsable(EditorBrowsableState.Never)]
123                 public Page Peek(int depth)
124                 {
125                         if (depth < 0)
126                         {
127                                 return null;
128                         }
129
130                         if (InternalChildren.Count <= depth)
131                         {
132                                 return null;
133                         }
134
135                         return (Page)InternalChildren[InternalChildren.Count - depth - 1];
136                 }
137
138         /// <summary>
139         /// For internal use.
140         /// </summary>
141                 [EditorBrowsable(EditorBrowsableState.Never)]
142                 public IEnumerable<Page> Pages => InternalChildren.Cast<Page>();
143
144         /// <summary>
145         /// For internal use
146         /// </summary>
147                 [EditorBrowsable(EditorBrowsableState.Never)]
148                 public int StackDepth
149                 {
150                         get { return InternalChildren.Count; }
151                 }
152
153         /// <summary>
154         /// The Page that is currently top-most on the navigation stack.
155         /// </summary>
156                 public Page CurrentPage
157                 {
158                         get { return (Page)GetValue(CurrentPageProperty); }
159                         private set { SetValue(CurrentPagePropertyKey, value); }
160                 }
161
162         /// <summary>
163         /// The Page that is the root of the navigation stack.
164         /// </summary>
165                 public Page RootPage
166                 {
167                         get { return (Page)GetValue(RootPageProperty); }
168                         private set { SetValue(RootPagePropertyKey, value); }
169                 }
170
171         /// <summary>
172         /// The title of the back button for the specified page.
173         /// </summary>
174         /// <param name="page">The Page whose back-button's title is being requested.</param>
175         /// <returns>The title of the back button that would be shown if the specified page were the Xamarin.Forms.CurrentPage.</returns>
176                 public static string GetBackButtonTitle(BindableObject page)
177                 {
178                         return (string)page.GetValue(BackButtonTitleProperty);
179                 }
180
181         /// <summary>
182         /// Returns a value that indicates whether page has a back button.
183         /// </summary>
184         /// <param name="page">The page to be checked</param>
185         /// <returns>true if the page has a back button.</returns>
186                 public static bool GetHasBackButton(Page page)
187                 {
188                         if (page == null)
189                                 throw new ArgumentNullException("page");
190                         return (bool)page.GetValue(HasBackButtonProperty);
191                 }
192
193         /// <summary>
194         /// Returns a value that indicates whether the page has a navigation bar.
195         /// </summary>
196         /// <param name="page">The Page being queried.</param>
197         /// <returns>true if page would display a navigation bar were it the CurrentPage.</returns>
198                 public static bool GetHasNavigationBar(BindableObject page)
199                 {
200                         return (bool)page.GetValue(HasNavigationBarProperty);
201                 }
202
203                 internal static FileImageSource GetTitleIcon(BindableObject bindable)
204                 {
205                         return (FileImageSource)bindable.GetValue(TitleIconProperty);
206                 }
207
208         /// <summary>
209         /// Asynchronously removes the top Page from the navigation stack.
210         /// </summary>
211         /// <returns>The Page that had been at the top of the navigation stack.</returns>
212                 public Task<Page> PopAsync()
213                 {
214                         return PopAsync(true);
215                 }
216
217         /// <summary>
218         /// Asynchronously removes the top Page from the navigation stack, with optional animation.
219         /// </summary>
220         /// <param name="animated">Whether to animate the pop.</param>
221         /// <returns>The Page that had been at the top of the navigation stack.</returns>
222                 public async Task<Page> PopAsync(bool animated)
223                 {
224                         var tcs = new TaskCompletionSource<bool>();
225                         if (CurrentNavigationTask != null && !CurrentNavigationTask.IsCompleted)
226                         {
227                                 var oldTask = CurrentNavigationTask;
228                                 CurrentNavigationTask = tcs.Task;
229                                 await oldTask;
230                         }
231                         else
232                                 CurrentNavigationTask = tcs.Task;
233
234                         var result = await PopAsyncInner(animated, false);
235                         tcs.SetResult(true);
236                         return result;
237                 }
238
239         /// <summary>
240         /// Event that is raised after a page is popped from this NavigationPage element.
241         /// </summary>
242                 public event EventHandler<NavigationEventArgs> Popped;
243
244         /// <summary>
245         /// Event that is raised when the last nonroot element is popped from this NavigationPage element.
246         /// </summary>
247                 public event EventHandler<NavigationEventArgs> PoppedToRoot;
248
249         /// <summary>
250         /// Pops all but the root Page off the navigation stack.
251         /// </summary>
252         /// <returns>A task that represents the asynchronous dismiss operation.</returns>
253                 public Task PopToRootAsync()
254                 {
255                         return PopToRootAsync(true);
256                 }
257
258         /// <summary>
259         /// A task for asynchronously popping all pages off of the navigation stack.
260         /// </summary>
261         /// <param name="animated">Whether to animate the pop.</param>
262         /// <returns>A task that represents the asynchronous dismiss operation.</returns>
263                 public async Task PopToRootAsync(bool animated)
264                 {
265                         if (CurrentNavigationTask != null && !CurrentNavigationTask.IsCompleted)
266                         {
267                                 var tcs = new TaskCompletionSource<bool>();
268                                 Task oldTask = CurrentNavigationTask;
269                                 CurrentNavigationTask = tcs.Task;
270                                 await oldTask;
271
272                                 await PopToRootAsyncInner(animated);
273                                 tcs.SetResult(true);
274                                 return;
275                         }
276
277                         Task result = PopToRootAsyncInner(animated);
278                         CurrentNavigationTask = result;
279                         await result;
280                 }
281
282         /// <summary>
283         /// Presents a Page modally.
284         /// </summary>
285         /// <param name="page">The Page to present modally.</param>
286         /// <returns>An awaitable Task, indicating the PushModal completion.</returns>
287                 public Task PushAsync(Page page)
288                 {
289                         return PushAsync(page, true);
290                 }
291
292         /// <summary>
293         /// A task for asynchronously pushing a page onto the navigation stack, with optional animation.
294         /// </summary>
295         /// <param name="page">The Page to present modally.</param>
296         /// <param name="animated">Whether to animate the pop.</param>
297         /// <returns>An awaitable Task, indicating the PushModal completion.</returns>
298                 public async Task PushAsync(Page page, bool animated)
299                 {
300                         if (CurrentNavigationTask != null && !CurrentNavigationTask.IsCompleted)
301                         {
302                                 var tcs = new TaskCompletionSource<bool>();
303                                 Task oldTask = CurrentNavigationTask;
304                                 CurrentNavigationTask = tcs.Task;
305                                 await oldTask;
306
307                                 await PushAsyncInner(page, animated);
308                                 tcs.SetResult(true);
309                                 return;
310                         }
311
312                         CurrentNavigationTask = PushAsyncInner(page, animated);
313                         await CurrentNavigationTask;
314                 }
315
316         /// <summary>
317         /// Event that is raised when a page is pushed onto this NavigationPage element.
318         /// </summary>
319                 public event EventHandler<NavigationEventArgs> Pushed;
320
321         /// <summary>
322         /// Sets the title that appears on the back button for page.
323         /// </summary>
324         /// <param name="page">The BindableObject object.</param>
325         /// <param name="value">The value to set.</param>
326                 public static void SetBackButtonTitle(BindableObject page, string value)
327                 {
328                         page.SetValue(BackButtonTitleProperty, value);
329                 }
330
331         /// <summary>
332         /// Adds or removes a back button to page, with optional animation.
333         /// </summary>
334         /// <param name="page">The page object.</param>
335         /// <param name="value">The value to set.</param>
336                 public static void SetHasBackButton(Page page, bool value)
337                 {
338                         if (page == null)
339                                 throw new ArgumentNullException("page");
340                         page.SetValue(HasBackButtonProperty, value);
341                 }
342
343         /// <summary>
344         /// Sets a value that indicates whether or not this NavigationPage element has a navigation bar.
345         /// </summary>
346         /// <param name="page">The BindableObject object</param>
347         /// <param name="value">The value to set</param>
348                 public static void SetHasNavigationBar(BindableObject page, bool value)
349                 {
350                         page.SetValue(HasNavigationBarProperty, value);
351                 }
352
353                 internal static void SetTitleIcon(BindableObject bindable, FileImageSource value)
354                 {
355                         bindable.SetValue(TitleIconProperty, value);
356                 }
357
358         /// <summary>
359         /// Event that is raised when the hardware back button is pressed.
360         /// </summary>
361         /// <returns>true if consumed</returns>
362                 protected override bool OnBackButtonPressed()
363                 {
364                         if (CurrentPage.SendBackButtonPressed())
365                                 return true;
366
367                         if (StackDepth > 1)
368                         {
369                                 SafePop();
370                                 return true;
371                         }
372
373                         return base.OnBackButtonPressed();
374                 }
375
376         /// <summary>
377         /// For internal use
378         /// </summary>
379                 [EditorBrowsable(EditorBrowsableState.Never)]
380                 public event EventHandler<NavigationRequestedEventArgs> InsertPageBeforeRequested;
381
382         /// <summary>
383         /// For internal use
384         /// </summary>
385         /// <param name="animated">Whether animate the pop.</param>
386         /// <param name="fast"></param>
387         /// <returns>A task that represents the asynchronous dismiss operation.</returns>
388                 [EditorBrowsable(EditorBrowsableState.Never)]
389                 public async Task<Page> PopAsyncInner(bool animated, bool fast)
390                 {
391                         if (StackDepth == 1)
392                         {
393                                 return null;
394                         }
395
396                         var page = (Page)InternalChildren.Last();
397
398                         return await (this as INavigationPageController).RemoveAsyncInner(page, animated, fast);
399                 }
400
401                 async Task<Page> INavigationPageController.RemoveAsyncInner(Page page, bool animated, bool fast)
402                 {
403                         if (StackDepth == 1)
404                         {
405                                 return null;
406                         }
407
408                         var args = new NavigationRequestedEventArgs(page, animated);
409
410                         var removed = true;
411
412                         EventHandler<NavigationRequestedEventArgs> requestPop = PopRequested;
413                         if (requestPop != null)
414                         {
415                                 requestPop(this, args);
416
417                                 if (args.Task != null && !fast)
418                                         removed = await args.Task;
419                         }
420
421                         if (!removed && !fast)
422                                 return CurrentPage;
423
424                         InternalChildren.Remove(page);
425
426                         CurrentPage = (Page)InternalChildren.Last();
427
428                         if (Popped != null)
429                                 Popped(this, args);
430
431                         return page;
432                 }
433
434         /// <summary>
435         /// For internal use.
436         /// </summary>
437                 [EditorBrowsable(EditorBrowsableState.Never)]
438                 public event EventHandler<NavigationRequestedEventArgs> PopRequested;
439
440         /// <summary>
441         /// For internal use.
442         /// </summary>
443                 [EditorBrowsable(EditorBrowsableState.Never)]
444                 public event EventHandler<NavigationRequestedEventArgs> PopToRootRequested;
445
446         /// <summary>
447         /// For internal use.
448         /// </summary>
449                 [EditorBrowsable(EditorBrowsableState.Never)]
450                 public event EventHandler<NavigationRequestedEventArgs> PushRequested;
451
452         /// <summary>
453         /// For internal use.
454         /// </summary>
455                 [EditorBrowsable(EditorBrowsableState.Never)]
456                 public event EventHandler<NavigationRequestedEventArgs> RemovePageRequested;
457
458                 void InsertPageBefore(Page page, Page before)
459                 {
460                         if (page == null)
461                                 throw new ArgumentNullException($"{nameof(page)} cannot be null.");
462
463                         if (before == null)
464                                 throw new ArgumentNullException($"{nameof(before)} cannot be null.");
465
466                         if (!InternalChildren.Contains(before))
467                                 throw new ArgumentException($"{nameof(before)} must be a child of the NavigationPage", nameof(before));
468
469                         if (InternalChildren.Contains(page))
470                                 throw new ArgumentException("Cannot insert page which is already in the navigation stack");
471
472                         EventHandler<NavigationRequestedEventArgs> handler = InsertPageBeforeRequested;
473                         handler?.Invoke(this, new NavigationRequestedEventArgs(page, before, false));
474
475                         int index = InternalChildren.IndexOf(before);
476                         InternalChildren.Insert(index, page);
477
478                         if (index == 0)
479                                 RootPage = page;
480
481                         // Shouldn't be required?
482                         // if (Width > 0 && Height > 0)
483                                 ForceLayout();
484                 }
485
486                 async Task PopToRootAsyncInner(bool animated)
487                 {
488                         if (StackDepth == 1)
489                                 return;
490
491                         Element[] childrenToRemove = InternalChildren.Skip(1).ToArray();
492                         foreach (Element child in childrenToRemove)
493                                 InternalChildren.Remove(child);
494
495                         CurrentPage = RootPage;
496
497                         var args = new NavigationRequestedEventArgs(RootPage, animated);
498
499                         EventHandler<NavigationRequestedEventArgs> requestPopToRoot = PopToRootRequested;
500                         if (requestPopToRoot != null)
501                         {
502                                 requestPopToRoot(this, args);
503
504                                 if (args.Task != null)
505                                         await args.Task;
506                         }
507
508                         // PoppedToRoot?.Invoke(this, new PoppedToRootEventArgs(RootPage, childrenToRemove.OfType<Page>().ToList()));
509                 }
510
511                 async Task PushAsyncInner(Page page, bool animated)
512                 {
513                         if (InternalChildren.Contains(page))
514                                 return;
515
516                         PushPage(page);
517
518                         var args = new NavigationRequestedEventArgs(page, animated);
519
520                         EventHandler<NavigationRequestedEventArgs> requestPush = PushRequested;
521                         if (requestPush != null)
522                         {
523                                 requestPush(this, args);
524
525                                 if (args.Task != null)
526                                         await args.Task;
527                         }
528
529                         Pushed?.Invoke(this, args);
530                 }
531
532                 void PushPage(Page page)
533                 {
534                         InternalChildren.Add(page);
535
536                         if (InternalChildren.Count == 1)
537                                 RootPage = page;
538
539                         CurrentPage = page;
540                 }
541
542                 void RemovePage(Page page)
543                 {
544                         if (page == null)
545                                 throw new ArgumentNullException($"{nameof(page)} cannot be null.");
546
547                         if (page == CurrentPage && CurrentPage == RootPage)
548                                 throw new InvalidOperationException("Cannot remove root page when it is also the currently displayed page.");
549                         if (page == CurrentPage)
550                         {
551                                 // Log.Warning("NavigationPage", "RemovePage called for CurrentPage object. This can result in undesired behavior, consider calling PopAsync instead.");
552                                 PopAsync();
553                                 return;
554                         }
555
556                         if (!InternalChildren.Contains(page))
557                                 throw new ArgumentException("Page to remove must be contained on this Navigation Page");
558
559                         EventHandler<NavigationRequestedEventArgs> handler = RemovePageRequested;
560                         handler?.Invoke(this, new NavigationRequestedEventArgs(page, true));
561
562                         InternalChildren.Remove(page);
563                         if (RootPage == page)
564                                 RootPage = (Page)InternalChildren.First();
565                 }
566
567                 void SafePop()
568                 {
569                         PopAsync(true).ContinueWith(t =>
570                         {
571                                 if (t.IsFaulted)
572                                         throw t.Exception;
573                         });
574                 }
575
576                 class NavigationImpl : NavigationProxy
577                 {
578                         // readonly Lazy<ReadOnlyCastingList<Page, Element>> _castingList;
579
580                         public NavigationImpl(NavigationPage owner)
581                         {
582                                 Owner = owner;
583                                 // _castingList = new Lazy<ReadOnlyCastingList<Page, Element>>(() => new ReadOnlyCastingList<Page, Element>(Owner.InternalChildren));
584                         }
585
586                         NavigationPage Owner { get; }
587
588                         protected override IReadOnlyList<Page> GetNavigationStack()
589                         {
590                                 // return _castingList.Value;
591                                 return null;
592                         }
593
594                         protected override void OnInsertPageBefore(Page page, Page before)
595                         {
596                                 Owner.InsertPageBefore(page, before);
597                         }
598
599                         protected override Task<Page> OnPopAsync(bool animated)
600                         {
601                                 return Owner.PopAsync(animated);
602                         }
603
604                         protected override Task OnPopToRootAsync(bool animated)
605                         {
606                                 return Owner.PopToRootAsync(animated);
607                         }
608
609                         protected override Task OnPushAsync(Page root, bool animated)
610                         {
611                                 return Owner.PushAsync(root, animated);
612                         }
613
614                         protected override void OnRemovePage(Page page)
615                         {
616                                 Owner.RemovePage(page);
617                         }
618                 }
619
620                 readonly Lazy<PlatformConfigurationRegistry<NavigationPage>> _platformConfigurationRegistry;
621
622         /// <summary>
623         /// Returns the platform-specific instance of this NavigationPage, on which a platform-specific method may be called.
624         /// </summary>
625         /// <typeparam name="T">The platform for which to return an instance.</typeparam>
626         /// <returns>The platform-specific instance of this NavigationPage</returns>
627                 public new IPlatformElementConfiguration<T, NavigationPage> On<T>() where T : IConfigPlatform
628                 {
629                         return _platformConfigurationRegistry.Value.On<T>();
630                 }
631         }
632 }