3d10f2835b5bbf04d310d60f84f9985fa188e2cb
[platform/core/csapi/tizenfx.git] / src / Tizen.NUI.Components / Controls / Navigation / Navigator.cs
1 /*
2  * Copyright(c) 2021 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.Diagnostics.CodeAnalysis;
22 using Tizen.NUI.BaseComponents;
23
24 namespace Tizen.NUI.Components
25 {
26     /// <summary>
27     /// The Navigator is a class which navigates pages with stack methods such
28     /// as Push and Pop.
29     /// </summary>
30     [EditorBrowsable(EditorBrowsableState.Never)]
31     public class Navigator : Control
32     {
33         //This will be replaced with view transition class instance.
34         private Animation curAnimation = null;
35
36         //This will be replaced with view transition class instance.
37         private Animation newAnimation = null;
38
39         //TODO: Needs to consider how to remove disposed window from dictionary.
40         //Two dictionaries are required to remove disposed navigator from dictionary.
41         private static Dictionary<Window, Navigator> windowNavigator = new Dictionary<Window, Navigator>();
42         private static Dictionary<Navigator, Window> navigatorWindow = new Dictionary<Navigator, Window>();
43
44         /// <summary>
45         /// Creates a new instance of a Navigator.
46         /// </summary>
47         [EditorBrowsable(EditorBrowsableState.Never)]
48         public Navigator() : base()
49         {
50             Layout = new AbsoluteLayout();
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         [SuppressMessage("Microsoft.Reliability",
381                          "CA2000:DisposeObjectsBeforeLosingScope",
382                          Justification = "The pushed views are added to NavigationPages and are disposed in Navigator.Dispose().")]
383         public static void ShowDialog(View content = null)
384         {
385             var window = NUIApplication.GetDefaultWindow();
386             var defaultNavigator = window.GetDefaultNavigator();
387
388             var dialog = new Dialog(content);
389             SetDialogScrim(dialog);
390
391             var dialogPage = new Page(dialog);
392             defaultNavigator.Push(dialogPage);
393         }
394
395         /// <summary>
396         /// Shows an alert dialog by pushing a page containing the alert dialog
397         /// to default navigator.
398         /// </summary>
399         /// <param name="titleContent">The title content of AlertDialog.</param>
400         /// <param name="content">The content of AlertDialog.</param>
401         /// <param name="actionContent">The action content of AlertDialog.</param>
402         [EditorBrowsable(EditorBrowsableState.Never)]
403         [SuppressMessage("Microsoft.Reliability",
404                          "CA2000:DisposeObjectsBeforeLosingScope",
405                          Justification = "The pushed views are added to NavigationPages and are disposed in Navigator.Dispose().")]
406         public static void ShowAlertDialog(View titleContent, View content, View actionContent)
407         {
408             var window = NUIApplication.GetDefaultWindow();
409             var defaultNavigator = window.GetDefaultNavigator();
410
411             var dialog = new AlertDialog(titleContent, content, actionContent);
412             SetDialogScrim(dialog);
413
414             var dialogPage = new Page(dialog);
415             defaultNavigator.Push(dialogPage);
416         }
417
418         /// <summary>
419         /// Shows an alert dialog by pushing a page containing the alert dialog
420         /// to default navigator.
421         /// </summary>
422         /// <param name="title">The title of AlertDialog.</param>
423         /// <param name="message">The message of AlertDialog.</param>
424         /// <param name="positiveButtonText">The positive button text in the action content of AlertDialog.</param>
425         /// <param name="positiveButtonClickedHandler">The clicked callback of the positive button in the action content of AlertDialog.</param>
426         /// <param name="negativeButtonText">The negative button text in the action content of AlertDialog.</param>
427         /// <param name="negativeButtonClickedHandler">The clicked callback of the negative button in the action content of AlertDialog.</param>
428         [EditorBrowsable(EditorBrowsableState.Never)]
429         [SuppressMessage("Microsoft.Reliability",
430                          "CA2000:DisposeObjectsBeforeLosingScope",
431                          Justification = "The pushed views are added to NavigationPages and are disposed in Navigator.Dispose().")]
432         public static void ShowAlertDialog(string title = null, string message = null, string positiveButtonText = null, EventHandler<ClickedEventArgs> positiveButtonClickedHandler = null, string negativeButtonText = null, EventHandler<ClickedEventArgs> negativeButtonClickedHandler = null)
433         {
434             var window = NUIApplication.GetDefaultWindow();
435             var defaultNavigator = window.GetDefaultNavigator();
436
437             var dialog = new AlertDialog(title, message, positiveButtonText, positiveButtonClickedHandler, negativeButtonText, negativeButtonClickedHandler);
438             SetDialogScrim(dialog);
439
440             var dialogPage = new Page(dialog);
441             defaultNavigator.Push(dialogPage);
442         }
443
444
445         private static void SetDialogScrim(Dialog dialog)
446         {
447             if (dialog == null)
448             {
449                 return;
450             }
451
452             var window = NUIApplication.GetDefaultWindow();
453             var defaultNavigator = window.GetDefaultNavigator();
454             var defaultScrim = dialog.Scrim;
455
456             //Copies default scrim's GUI properties.
457             var scrim = new VisualView();
458             scrim.BackgroundColor = defaultScrim.BackgroundColor;
459             scrim.Size = defaultScrim.Size;
460             scrim.TouchEvent += (object source, View.TouchEventArgs e) =>
461             {
462                 if (e.Touch.GetState(0) == PointStateType.Up)
463                 {
464                     defaultNavigator.Pop();
465                 }
466
467                 return true;
468             };
469
470             dialog.Scrim = scrim;
471         }
472     }
473 } //namespace Tizen.NUI