[NUI] Rebase DevelNUI (#2507)
[platform/core/csapi/tizenfx.git] / src / Tizen.NUI.Components / Controls / Navigation / Navigator.cs
1 /*
2  * Copyright(c) 2020 Samsung Electronics Co., Ltd.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  *
16  */
17
18 using System;
19 using System.Collections.Generic;
20 using System.ComponentModel;
21 using System.Threading.Tasks;
22 using Tizen.NUI.BaseComponents;
23 using Tizen.NUI.Binding;
24
25 namespace Tizen.NUI.Components
26 {
27     /// <summary>
28     /// The Navigator is a class which navigates pages with stack methods such
29     /// as Push and Pop.
30     /// </summary>
31     [EditorBrowsable(EditorBrowsableState.Never)]
32     public class Navigator : Control
33     {
34         //This will be replaced with view transition class instance.
35         private Animation _curAnimation = null;
36
37         //This will be replaced with view transition class instance.
38         private Animation _newAnimation = null;
39
40         //TODO: Needs to consider how to remove disposed window from dictionary.
41         //Two dictionaries are required to remove disposed navigator from dictionary.
42         private static Dictionary<Window, Navigator> windowNavigator = new Dictionary<Window, Navigator>();
43         private static Dictionary<Navigator, Window> navigatorWindow = new Dictionary<Navigator, Window>();
44
45         /// <summary>
46         /// Creates a new instance of a Navigator.
47         /// </summary>
48         [EditorBrowsable(EditorBrowsableState.Never)]
49         public Navigator() : base()
50         {
51         }
52
53         /// <summary>
54         /// List of pages of Navigator.
55         /// </summary>
56         [EditorBrowsable(EditorBrowsableState.Never)]
57         public List<Page> NavigationPages { get; } = new List<Page>();
58
59         /// <summary>
60         /// Pushes a page to Navigator.
61         /// If the page is already in Navigator, then it is not pushed.
62         /// </summary>
63         /// <param name="page">The page to push to Navigator.</param>
64         /// <exception cref="ArgumentNullException">Thrown when the argument page is null.</exception>
65         [EditorBrowsable(EditorBrowsableState.Never)]
66         public void Push(Page page)
67         {
68             if (page == null)
69             {
70                 throw new ArgumentNullException(nameof(page), "page should not be null.");
71             }
72
73             //Duplicate page is not pushed.
74             if (NavigationPages.Contains(page)) return;
75
76             var curTop = Peek();
77
78             if (!curTop)
79             {
80                 Insert(0, page);
81                 return;
82             }
83
84             NavigationPages.Add(page);
85             Add(page);
86
87             //Invoke Page events
88             page.InvokeAppearing();
89             curTop.InvokeDisappearing();
90
91             //TODO: The following transition codes will be replaced with view transition.
92             if (_curAnimation)
93             {
94                 _curAnimation.Stop();
95                 _curAnimation.Clear();
96             }
97
98             if (_newAnimation)
99             {
100                 _newAnimation.Stop();
101                 _newAnimation.Clear();
102             }
103
104             _curAnimation = new Animation(1000);
105             using (var scaleVec = new Vector3(0.0f, 0.0f, 1.0f))
106             {
107                 _curAnimation.AnimateTo(curTop, "Scale", scaleVec, 0, 1000);
108             }
109             _curAnimation.AnimateTo(curTop, "Opacity", 0.0f, 0, 1000);
110             _curAnimation.EndAction = Animation.EndActions.Discard;
111             _curAnimation.Play();
112
113             using (var scaleVec = new Vector3(0.0f, 0.0f, 1.0f))
114             {
115                 using (var scaleProp = new Tizen.NUI.PropertyValue(scaleVec))
116                 {
117                     Tizen.NUI.Object.SetProperty(page.SwigCPtr, Page.Property.SCALE, scaleProp);
118                 }
119             }
120             using (var scaleProp = new Tizen.NUI.PropertyValue(0.0f))
121             {
122                 Tizen.NUI.Object.SetProperty(page.SwigCPtr, Page.Property.OPACITY, scaleProp);
123             }
124             _newAnimation = new Animation(1000);
125             using (var scaleVec = new Vector3(1.0f, 1.0f, 1.0f))
126             {
127                 _newAnimation.AnimateTo(page, "Scale", scaleVec, 0, 1000);
128             }
129             _newAnimation.AnimateTo(page, "Opacity", 1.0f, 0, 1000);
130             _newAnimation.Play();
131         }
132
133         /// <summary>
134         /// Pops the top page from Navigator.
135         /// </summary>
136         /// <returns>The popped page.</returns>
137         /// <exception cref="InvalidOperationException">Thrown when there is no page in Navigator.</exception>
138         [EditorBrowsable(EditorBrowsableState.Never)]
139         public Page Pop()
140         {
141             if (NavigationPages.Count == 0)
142             {
143                 throw new InvalidOperationException("There is no page in Navigator.");
144             }
145
146             var curTop = Peek();
147
148             if (NavigationPages.Count == 1)
149             {
150                 Remove(curTop);
151                 return curTop;
152             }
153
154             var newTop = NavigationPages[NavigationPages.Count - 2];
155
156             //Invoke Page events
157             newTop.InvokeAppearing();
158             curTop.InvokeDisappearing();
159
160             //TODO: The following transition codes will be replaced with view transition.
161             if (_curAnimation)
162             {
163                 _curAnimation.Stop();
164                 _curAnimation.Clear();
165             }
166
167             if (_newAnimation)
168             {
169                 _newAnimation.Stop();
170                 _newAnimation.Clear();
171             }
172
173             _curAnimation = new Animation(1000);
174             using (var scaleVec = new Vector3(0.0f, 0.0f, 1.0f))
175             {
176                 _curAnimation.AnimateTo(curTop, "Scale", scaleVec, 0, 1000);
177             }
178             _curAnimation.AnimateTo(curTop, "Opacity", 0.0f, 0, 1000);
179             _curAnimation.Play();
180             _curAnimation.Finished += (object sender, EventArgs e) =>
181             {
182                 //Removes the current top page after transition is finished.
183                 Remove(curTop);
184             };
185
186             using (var scaleVec = new Vector3(0.0f, 0.0f, 1.0f))
187             {
188                 using (var scaleProp = new Tizen.NUI.PropertyValue(scaleVec))
189                 {
190                     Tizen.NUI.Object.SetProperty(newTop.SwigCPtr, Page.Property.SCALE, scaleProp);
191                 }
192             }
193             using (var opacityProp = new Tizen.NUI.PropertyValue(0.0f))
194             {
195                 Tizen.NUI.Object.SetProperty(newTop.SwigCPtr, Page.Property.OPACITY, opacityProp);
196             }
197             _newAnimation = new Animation(1000);
198             using (var scaleVec = new Vector3(1.0f, 1.0f, 1.0f))
199             {
200                 _newAnimation.AnimateTo(newTop, "Scale", scaleVec, 0, 1000);
201             }
202             _newAnimation.AnimateTo(newTop, "Opacity", 1.0f, 0, 1000);
203             _newAnimation.Play();
204
205             return curTop;
206         }
207
208         /// <summary>
209         /// Inserts a page at the specified index of Navigator.
210         /// If the page is already in Navigator, then it is not inserted.
211         /// </summary>
212         /// <param name="index">The index of Navigator where a page will be inserted.</param>
213         /// <param name="page">The page to insert to Navigator.</param>
214         /// <exception cref="ArgumentOutOfRangeException">Thrown when the argument index is less than 0, or greater than the number of pages.</exception>
215         /// <exception cref="ArgumentNullException">Thrown when the argument page is null.</exception>
216         [EditorBrowsable(EditorBrowsableState.Never)]
217         public void Insert(int index, Page page)
218         {
219             if ((index < 0) || (index > NavigationPages.Count))
220             {
221                 throw new ArgumentOutOfRangeException(nameof(index), "index should be greater than or equal to 0, and less than or equal to the number of pages.");
222             }
223
224             if (page == null)
225             {
226                 throw new ArgumentNullException(nameof(page), "page should not be null.");
227             }
228
229             //Duplicate page is not pushed.
230             if (NavigationPages.Contains(page)) return;
231
232             NavigationPages.Insert(index, page);
233             Add(page);
234         }
235
236         /// <summary>
237         /// Inserts a page to Navigator before an existing page.
238         /// If the page is already in Navigator, then it is not inserted.
239         /// </summary>
240         /// <param name="before">The existing page, before which a page will be inserted.</param>
241         /// <param name="page">The page to insert to Navigator.</param>
242         /// <exception cref="ArgumentNullException">Thrown when the argument before is null.</exception>
243         /// <exception cref="ArgumentNullException">Thrown when the argument page is null.</exception>
244         /// <exception cref="ArgumentException">Thrown when the argument before does not exist in Navigator.</exception>
245         [EditorBrowsable(EditorBrowsableState.Never)]
246         public void InsertBefore(Page before, Page page)
247         {
248             if (before == null)
249             {
250                 throw new ArgumentNullException(nameof(before), "before should not be null.");
251             }
252
253             if (page == null)
254             {
255                 throw new ArgumentNullException(nameof(page), "page should not be null.");
256             }
257
258             //Find the index of before page.
259             int beforeIndex = NavigationPages.FindIndex(x => x == before);
260
261             //before does not exist in Navigator.
262             if (beforeIndex == -1)
263             {
264                 throw new ArgumentException("before does not exist in Navigator.", nameof(before));
265             }
266
267             Insert(beforeIndex, page);
268         }
269
270         /// <summary>
271         /// Removes a page from Navigator.
272         /// </summary>
273         /// <param name="page">The page to remove from Navigator.</param>
274         /// <exception cref="ArgumentNullException">Thrown when the argument page is null.</exception>
275         [EditorBrowsable(EditorBrowsableState.Never)]
276         public void Remove(Page page)
277         {
278             if (page == null)
279             {
280                 throw new ArgumentNullException(nameof(page), "page should not be null.");
281             }
282
283             NavigationPages.Remove(page);
284             base.Remove(page);
285         }
286
287         /// <summary>
288         /// Removes a page at the specified index of Navigator.
289         /// </summary>
290         /// <param name="index">The index of Navigator where a page will be removed.</param>
291         /// <exception cref="ArgumentOutOfRangeException">Thrown when the index is less than 0, or greater than or equal to the number of pages.</exception>
292         [EditorBrowsable(EditorBrowsableState.Never)]
293         public void RemoveAt(int index)
294         {
295             if ((index < 0) || (index >= NavigationPages.Count))
296             {
297                 throw new ArgumentOutOfRangeException(nameof(index), "index should be greater than or equal to 0, and less than the number of pages.");
298             }
299
300             Remove(NavigationPages[index]);
301         }
302
303         /// <summary>
304         /// Returns the page at the top of Navigator.
305         /// </summary>
306         /// <returns>The page at the top of Navigator.</returns>
307         [EditorBrowsable(EditorBrowsableState.Never)]
308         public Page Peek()
309         {
310             if (NavigationPages.Count == 0) return null;
311
312             return NavigationPages[NavigationPages.Count - 1];
313         }
314
315         /// <summary>
316         /// Disposes Navigator and all children on it.
317         /// </summary>
318         /// <param name="type">Dispose type.</param>
319         [EditorBrowsable(EditorBrowsableState.Never)]
320         protected override void Dispose(DisposeTypes type)
321         {
322             if (disposed)
323             {
324                 return;
325             }
326
327             if (type == DisposeTypes.Explicit)
328             {
329                 foreach (Page page in NavigationPages)
330                 {
331                     Utility.Dispose(page);
332                 }
333                 NavigationPages.Clear();
334
335                 Window window;
336
337                 if (navigatorWindow.TryGetValue(this, out window) == true)
338                 {
339                     navigatorWindow.Remove(this);
340                     windowNavigator.Remove(window);
341                 }
342             }
343
344             base.Dispose(type);
345         }
346
347         /// <summary>
348         /// Returns the default navigator of the given window.
349         /// </summary>
350         /// <returns>The default navigator of the given window.</returns>
351         /// <exception cref="ArgumentNullException">Thrown when the argument window is null.</exception>
352         [EditorBrowsable(EditorBrowsableState.Never)]
353         public static Navigator GetDefaultNavigator(Window window)
354         {
355             if (window == null)
356             {
357                 throw new ArgumentNullException(nameof(window), "window should not be null.");
358             }
359
360             if (windowNavigator.ContainsKey(window) == true)
361             {
362                 return windowNavigator[window];
363             }
364
365             var defaultNavigator = new Navigator();
366             defaultNavigator.WidthResizePolicy = ResizePolicyType.FillToParent;
367             defaultNavigator.HeightResizePolicy = ResizePolicyType.FillToParent;
368             window.Add(defaultNavigator);
369             windowNavigator.Add(window, defaultNavigator);
370             navigatorWindow.Add(defaultNavigator, window);
371
372             return defaultNavigator;
373         }
374
375         /// <summary>
376         /// Shows a dialog by pushing a page containing dialog to default navigator.
377         /// </summary>
378         /// <param name="content">The content of Dialog.</param>
379         [EditorBrowsable(EditorBrowsableState.Never)]
380         public static void ShowDialog(View content = null)
381         {
382             var window = NUIApplication.GetDefaultWindow();
383             var defaultNavigator = window.GetDefaultNavigator();
384
385             var dialog = new Dialog(content);
386             SetDialogScrim(dialog);
387
388             var dialogPage = new Page(dialog);
389             defaultNavigator.Push(dialogPage);
390         }
391
392         /// <summary>
393         /// Shows an alert dialog by pushing a page containing the alert dialog
394         /// to default navigator.
395         /// </summary>
396         /// <param name="titleContent">The title content of AlertDialog.</param>
397         /// <param name="content">The content of AlertDialog.</param>
398         /// <param name="actionContent">The action content of AlertDialog.</param>
399         [EditorBrowsable(EditorBrowsableState.Never)]
400         public static void ShowAlertDialog(View titleContent, View content, View actionContent)
401         {
402             var window = NUIApplication.GetDefaultWindow();
403             var defaultNavigator = window.GetDefaultNavigator();
404
405             var dialog = new AlertDialog(titleContent, content, actionContent);
406             SetDialogScrim(dialog);
407
408             var dialogPage = new Page(dialog);
409             defaultNavigator.Push(dialogPage);
410         }
411
412         /// <summary>
413         /// Shows an alert dialog by pushing a page containing the alert dialog
414         /// to default navigator.
415         /// </summary>
416         /// <param name="title">The title of AlertDialog.</param>
417         /// <param name="message">The message of AlertDialog.</param>
418         /// <param name="positiveButtonText">The positive button text in the action content of AlertDialog.</param>
419         /// <param name="positiveButtonClickedHandler">The clicked callback of the positive button in the action content of AlertDialog.</param>
420         /// <param name="negativeButtonText">The negative button text in the action content of AlertDialog.</param>
421         /// <param name="negativeButtonClickedHandler">The clicked callback of the negative button in the action content of AlertDialog.</param>
422         [EditorBrowsable(EditorBrowsableState.Never)]
423         public static void ShowAlertDialog(string title = null, string message = null, string positiveButtonText = null, EventHandler<ClickedEventArgs> positiveButtonClickedHandler = null, string negativeButtonText = null, EventHandler<ClickedEventArgs> negativeButtonClickedHandler = null)
424         {
425             var window = NUIApplication.GetDefaultWindow();
426             var defaultNavigator = window.GetDefaultNavigator();
427
428             var dialog = new AlertDialog(title, message, positiveButtonText, positiveButtonClickedHandler, negativeButtonText, negativeButtonClickedHandler);
429             SetDialogScrim(dialog);
430
431             var dialogPage = new Page(dialog);
432             defaultNavigator.Push(dialogPage);
433         }
434
435
436         private static void SetDialogScrim(Dialog dialog)
437         {
438             if (dialog == null)
439             {
440                 return;
441             }
442
443             var window = NUIApplication.GetDefaultWindow();
444             var defaultNavigator = window.GetDefaultNavigator();
445             var defaultScrim = dialog.Scrim;
446
447             //Copies default scrim's GUI properties.
448             var scrim = new VisualView();
449             scrim.BackgroundColor = defaultScrim.BackgroundColor;
450             scrim.Size = defaultScrim.Size;
451             scrim.TouchEvent += (object source, View.TouchEventArgs e) =>
452             {
453                 if (e.Touch.GetState(0) == PointStateType.Up)
454                 {
455                     defaultNavigator.Pop();
456                 }
457
458                 return true;
459             };
460
461             dialog.Scrim = scrim;
462         }
463     }
464 } //namespace Tizen.NUI