[NUI] Change GetDefaultWindow() to static func (#900)
[platform/core/csapi/tizenfx.git] / src / Tizen.NUI / src / internal / 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.Binding.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     internal 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 Tizen.NUI.Xaml.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         /// Pops all but the root Page off the navigation stack.
246         /// </summary>
247         /// <returns>A task that represents the asynchronous dismiss operation.</returns>
248         public Task PopToRootAsync()
249         {
250             return PopToRootAsync(true);
251         }
252
253         /// <summary>
254         /// A task for asynchronously popping all pages off of the navigation stack.
255         /// </summary>
256         /// <param name="animated">Whether to animate the pop.</param>
257         /// <returns>A task that represents the asynchronous dismiss operation.</returns>
258         public async Task PopToRootAsync(bool animated)
259         {
260             if (CurrentNavigationTask != null && !CurrentNavigationTask.IsCompleted)
261             {
262                 var tcs = new TaskCompletionSource<bool>();
263                 Task oldTask = CurrentNavigationTask;
264                 CurrentNavigationTask = tcs.Task;
265                 await oldTask;
266
267                 await PopToRootAsyncInner(animated);
268                 tcs.SetResult(true);
269                 return;
270             }
271
272             Task result = PopToRootAsyncInner(animated);
273             CurrentNavigationTask = result;
274             await result;
275         }
276
277         /// <summary>
278         /// Presents a Page modally.
279         /// </summary>
280         /// <param name="page">The Page to present modally.</param>
281         /// <returns>An awaitable Task, indicating the PushModal completion.</returns>
282         public Task PushAsync(Page page)
283         {
284             return PushAsync(page, true);
285         }
286
287         /// <summary>
288         /// A task for asynchronously pushing a page onto the navigation stack, with optional animation.
289         /// </summary>
290         /// <param name="page">The Page to present modally.</param>
291         /// <param name="animated">Whether to animate the pop.</param>
292         /// <returns>An awaitable Task, indicating the PushModal completion.</returns>
293         public async Task PushAsync(Page page, bool animated)
294         {
295             if (CurrentNavigationTask != null && !CurrentNavigationTask.IsCompleted)
296             {
297                 var tcs = new TaskCompletionSource<bool>();
298                 Task oldTask = CurrentNavigationTask;
299                 CurrentNavigationTask = tcs.Task;
300                 await oldTask;
301
302                 await PushAsyncInner(page, animated);
303                 tcs.SetResult(true);
304                 return;
305             }
306
307             CurrentNavigationTask = PushAsyncInner(page, animated);
308             await CurrentNavigationTask;
309         }
310
311         /// <summary>
312         /// Event that is raised when a page is pushed onto this NavigationPage element.
313         /// </summary>
314         public event EventHandler<NavigationEventArgs> Pushed;
315
316         /// <summary>
317         /// Sets the title that appears on the back button for page.
318         /// </summary>
319         /// <param name="page">The BindableObject object.</param>
320         /// <param name="value">The value to set.</param>
321         public static void SetBackButtonTitle(BindableObject page, string value)
322         {
323             page.SetValue(BackButtonTitleProperty, value);
324         }
325
326         /// <summary>
327         /// Adds or removes a back button to page, with optional animation.
328         /// </summary>
329         /// <param name="page">The page object.</param>
330         /// <param name="value">The value to set.</param>
331         public static void SetHasBackButton(Page page, bool value)
332         {
333             if (page == null)
334                 throw new ArgumentNullException("page");
335             page.SetValue(HasBackButtonProperty, value);
336         }
337
338         /// <summary>
339         /// Sets a value that indicates whether or not this NavigationPage element has a navigation bar.
340         /// </summary>
341         /// <param name="page">The BindableObject object</param>
342         /// <param name="value">The value to set</param>
343         public static void SetHasNavigationBar(BindableObject page, bool value)
344         {
345             page.SetValue(HasNavigationBarProperty, value);
346         }
347
348         internal static void SetTitleIcon(BindableObject bindable, FileImageSource value)
349         {
350             bindable.SetValue(TitleIconProperty, value);
351         }
352
353         /// <summary>
354         /// Event that is raised when the hardware back button is pressed.
355         /// </summary>
356         /// <returns>true if consumed</returns>
357         protected override bool OnBackButtonPressed()
358         {
359             if (CurrentPage.SendBackButtonPressed())
360                 return true;
361
362             if (StackDepth > 1)
363             {
364                 SafePop();
365                 return true;
366             }
367
368             return base.OnBackButtonPressed();
369         }
370
371         /// <summary>
372         /// For internal use
373         /// </summary>
374         [EditorBrowsable(EditorBrowsableState.Never)]
375         public event EventHandler<NavigationRequestedEventArgs> InsertPageBeforeRequested;
376
377         /// <summary>
378         /// For internal use
379         /// </summary>
380         /// <param name="animated">Whether animate the pop.</param>
381         /// <param name="fast"></param>
382         /// <returns>A task that represents the asynchronous dismiss operation.</returns>
383         [EditorBrowsable(EditorBrowsableState.Never)]
384         public async Task<Page> PopAsyncInner(bool animated, bool fast)
385         {
386             if (StackDepth == 1)
387             {
388                 return null;
389             }
390
391             var page = (Page)InternalChildren.Last();
392
393             return await (this as INavigationPageController).RemoveAsyncInner(page, animated, fast);
394         }
395
396         async Task<Page> INavigationPageController.RemoveAsyncInner(Page page, bool animated, bool fast)
397         {
398             if (StackDepth == 1)
399             {
400                 return null;
401             }
402
403             var args = new NavigationRequestedEventArgs(page, animated);
404
405             var removed = true;
406
407             EventHandler<NavigationRequestedEventArgs> requestPop = PopRequested;
408             if (requestPop != null)
409             {
410                 requestPop(this, args);
411
412                 if (args.Task != null && !fast)
413                     removed = await args.Task;
414             }
415
416             if (!removed && !fast)
417                 return CurrentPage;
418
419             InternalChildren.Remove(page);
420
421             CurrentPage = (Page)InternalChildren.Last();
422
423             if (Popped != null)
424                 Popped(this, args);
425
426             return page;
427         }
428
429         /// <summary>
430         /// For internal use.
431         /// </summary>
432         [EditorBrowsable(EditorBrowsableState.Never)]
433         public event EventHandler<NavigationRequestedEventArgs> PopRequested;
434
435         /// <summary>
436         /// For internal use.
437         /// </summary>
438         [EditorBrowsable(EditorBrowsableState.Never)]
439         public event EventHandler<NavigationRequestedEventArgs> PopToRootRequested;
440
441         /// <summary>
442         /// For internal use.
443         /// </summary>
444         [EditorBrowsable(EditorBrowsableState.Never)]
445         public event EventHandler<NavigationRequestedEventArgs> PushRequested;
446
447         /// <summary>
448         /// For internal use.
449         /// </summary>
450         [EditorBrowsable(EditorBrowsableState.Never)]
451         public event EventHandler<NavigationRequestedEventArgs> RemovePageRequested;
452
453         void InsertPageBefore(Page page, Page before)
454         {
455             if (page == null)
456                 throw new ArgumentNullException($"{nameof(page)} cannot be null.");
457
458             if (before == null)
459                 throw new ArgumentNullException($"{nameof(before)} cannot be null.");
460
461             if (!InternalChildren.Contains(before))
462                 throw new ArgumentException($"{nameof(before)} must be a child of the NavigationPage", nameof(before));
463
464             if (InternalChildren.Contains(page))
465                 throw new ArgumentException("Cannot insert page which is already in the navigation stack");
466
467             EventHandler<NavigationRequestedEventArgs> handler = InsertPageBeforeRequested;
468             handler?.Invoke(this, new NavigationRequestedEventArgs(page, before, false));
469
470             int index = InternalChildren.IndexOf(before);
471             InternalChildren.Insert(index, page);
472
473             if (index == 0)
474                 RootPage = page;
475
476             // Shouldn't be required?
477             // if (Width > 0 && Height > 0)
478                 ForceLayout();
479         }
480
481         async Task PopToRootAsyncInner(bool animated)
482         {
483             if (StackDepth == 1)
484                 return;
485
486             Element[] childrenToRemove = InternalChildren.Skip(1).ToArray();
487             foreach (Element child in childrenToRemove)
488                 InternalChildren.Remove(child);
489
490             CurrentPage = RootPage;
491
492             var args = new NavigationRequestedEventArgs(RootPage, animated);
493
494             EventHandler<NavigationRequestedEventArgs> requestPopToRoot = PopToRootRequested;
495             if (requestPopToRoot != null)
496             {
497                 requestPopToRoot(this, args);
498
499                 if (args.Task != null)
500                     await args.Task;
501             }
502
503             // PoppedToRoot?.Invoke(this, new PoppedToRootEventArgs(RootPage, childrenToRemove.OfType<Page>().ToList()));
504         }
505
506         async Task PushAsyncInner(Page page, bool animated)
507         {
508             if (InternalChildren.Contains(page))
509                 return;
510
511             PushPage(page);
512
513             var args = new NavigationRequestedEventArgs(page, animated);
514
515             EventHandler<NavigationRequestedEventArgs> requestPush = PushRequested;
516             if (requestPush != null)
517             {
518                 requestPush(this, args);
519
520                 if (args.Task != null)
521                     await args.Task;
522             }
523
524             Pushed?.Invoke(this, args);
525         }
526
527         void PushPage(Page page)
528         {
529             InternalChildren.Add(page);
530
531             if (InternalChildren.Count == 1)
532                 RootPage = page;
533
534             CurrentPage = page;
535         }
536
537         void RemovePage(Page page)
538         {
539             if (page == null)
540                 throw new ArgumentNullException($"{nameof(page)} cannot be null.");
541
542             if (page == CurrentPage && CurrentPage == RootPage)
543                 throw new InvalidOperationException("Cannot remove root page when it is also the currently displayed page.");
544             if (page == CurrentPage)
545             {
546                 // Log.Warning("NavigationPage", "RemovePage called for CurrentPage object. This can result in undesired behavior, consider calling PopAsync instead.");
547                 PopAsync();
548                 return;
549             }
550
551             if (!InternalChildren.Contains(page))
552                 throw new ArgumentException("Page to remove must be contained on this Navigation Page");
553
554             EventHandler<NavigationRequestedEventArgs> handler = RemovePageRequested;
555             handler?.Invoke(this, new NavigationRequestedEventArgs(page, true));
556
557             InternalChildren.Remove(page);
558             if (RootPage == page)
559                 RootPage = (Page)InternalChildren.First();
560         }
561
562         void SafePop()
563         {
564             PopAsync(true).ContinueWith(t =>
565             {
566                 if (t.IsFaulted)
567                     throw t.Exception;
568             });
569         }
570
571         class NavigationImpl : NavigationProxy
572         {
573             // readonly Lazy<ReadOnlyCastingList<Page, Element>> _castingList;
574
575             public NavigationImpl(NavigationPage owner)
576             {
577                 Owner = owner;
578                 // _castingList = new Lazy<ReadOnlyCastingList<Page, Element>>(() => new ReadOnlyCastingList<Page, Element>(Owner.InternalChildren));
579             }
580
581             NavigationPage Owner { get; }
582
583             protected override IReadOnlyList<Page> GetNavigationStack()
584             {
585                 // return _castingList.Value;
586                 return null;
587             }
588
589             protected override void OnInsertPageBefore(Page page, Page before)
590             {
591                 Owner.InsertPageBefore(page, before);
592             }
593
594             protected override Task<Page> OnPopAsync(bool animated)
595             {
596                 return Owner.PopAsync(animated);
597             }
598
599             protected override Task OnPopToRootAsync(bool animated)
600             {
601                 return Owner.PopToRootAsync(animated);
602             }
603
604             protected override Task OnPushAsync(Page root, bool animated)
605             {
606                 return Owner.PushAsync(root, animated);
607             }
608
609             protected override void OnRemovePage(Page page)
610             {
611                 Owner.RemovePage(page);
612             }
613         }
614
615         readonly Lazy<PlatformConfigurationRegistry<NavigationPage>> _platformConfigurationRegistry;
616
617         /// <summary>
618         /// Returns the platform-specific instance of this NavigationPage, on which a platform-specific method may be called.
619         /// </summary>
620         /// <typeparam name="T">The platform for which to return an instance.</typeparam>
621         /// <returns>The platform-specific instance of this NavigationPage</returns>
622         public new IPlatformElementConfiguration<T, NavigationPage> On<T>() where T : IConfigPlatform
623         {
624             return _platformConfigurationRegistry.Value.On<T>();
625         }
626     }
627 }