Support new features of Tizen.CircularUI (#188)
[platform/core/csapi/xsf.git] / src / XSF / Tizen.Wearable.CircularUI.Forms.Renderer / Shell / NavigationDrawer.cs
1 using ElmSharp;
2 using ElmSharp.Wearable;
3 using System;
4 using System.Threading;
5 using System.Threading.Tasks;
6 using Xamarin.Forms;
7 using Xamarin.Forms.Platform.Tizen;
8 using EColor = ElmSharp.Color;
9 using EImage = ElmSharp.Image;
10 using ELayout = ElmSharp.Layout;
11 using EWidget = ElmSharp.Widget;
12 using EButton = ElmSharp.Button;
13
14 namespace Tizen.Wearable.CircularUI.Forms.Renderer
15 {
16     public class NavigationDrawer : ELayout, IAnimatable
17     {
18         static readonly int TouchWidth = 50;
19         static readonly int IconSize = 40;
20         static readonly string DefaultIcon = "Tizen.Wearable.CircularUI.Forms.Renderer.res.wc_visual_cue.png";
21
22         Box _mainLayout;
23         Box _contentGestureBox;
24         Box _contentBox;
25         Box _drawerBox;
26         Box _drawerContentBox;
27         Box _drawerIconBox;
28
29         EvasObject _content;
30         EvasObject _drawerContent;
31
32         EImage _drawerIcon;
33         EButton _touchArea;
34
35         GestureLayer _gestureOnContent;
36         GestureLayer _gestureOnDrawer;
37
38         ImageSource _drawerIconSource;
39
40         bool _isOpen;
41         bool _isDefaultIcon;
42
43         CancellationTokenSource _fadeInCancelTokenSource = null;
44
45         bool HasDrawer => _drawerBox != null;
46
47         public NavigationDrawer(EvasObject parent) : base(parent)
48         {
49             Initialize();
50         }
51
52         public int HandlerHeight { get; set; } = 40;
53
54         public bool IsOpen
55         {
56             get
57             {
58                 return _isOpen;
59             }
60             set
61             {
62                 if (_isOpen != value)
63                 {
64                     if (value)
65                     {
66                         Open();
67                     }
68                     else
69                     {
70                         Close();
71                     }
72                 }
73             }
74         }
75
76         EColor _handlerBackgroundColor = EColor.Transparent;
77         public EColor HandlerBackgroundColor
78         {
79             get => _handlerBackgroundColor;
80             set
81             {
82                 _handlerBackgroundColor = value;
83                 UpdateHandlerBackgroundColor();
84             }
85         }
86
87         public event EventHandler Toggled;
88
89         public void SetMainContent(EvasObject content)
90         {
91             if (content == null)
92             {
93                 UnsetMainContent();
94                 return;
95             }
96
97             _content = content;
98             _content.Show();
99             _contentBox.PackEnd(_content);
100             _content.Geometry = _contentBox.Geometry;
101         }
102
103         public void SetDrawerContent(EvasObject content)
104         {
105             InitializeDrawerBox();
106
107             if (content == null)
108             {
109                 UnsetDrawerContent();
110                 return;
111             }
112
113             _drawerContent = content;
114             _drawerContent.Show();
115             _drawerContentBox.PackEnd(_drawerContent);
116
117             _drawerContentBox.Show();
118             _drawerIconBox.Show();
119
120             if (_drawerContent is NavigationView nv)
121             {
122                 nv.Dragged += (s, e) =>
123                 {
124                     if (e.State == DraggedState.EdgeTop)
125                     {
126                         Close();
127                     }
128                 };
129             }
130         }
131
132         public void UpdateDrawerIcon(ImageSource source)
133         {
134             _drawerIconSource = source;
135             if (HasDrawer)
136             {
137                 SetDrawerIcon(_drawerIconSource);
138             }
139         }
140
141         public async void Open(uint length = 300)
142         {
143             if (!HasDrawer)
144                 return;
145
146             var toMove = _drawerBox.Geometry;
147             toMove.Y = 0;
148
149             await RunMoveAnimation(_drawerBox, toMove, length);
150
151             if (!_isOpen)
152             {
153                 _isOpen = true;
154                 Toggled?.Invoke(this, EventArgs.Empty);
155             }
156             OnLayout();
157             OnDrawerLayout();
158             FlipIcon();
159         }
160
161         public async void Close(uint length = 300)
162         {
163             if (!HasDrawer)
164                 return;
165
166             var toMove = _drawerBox.Geometry;
167             toMove.Y = Geometry.Height - HandlerHeight;
168
169             await RunMoveAnimation(_drawerBox, toMove, length);
170
171             if (_isOpen)
172             {
173                 _isOpen = false;
174                 Toggled?.Invoke(this, EventArgs.Empty);
175             }
176             OnLayout();
177             OnDrawerLayout();
178             ResetIcon();
179             StartHighlightAnimation(_drawerIcon);
180         }
181
182         void IAnimatable.BatchBegin()
183         {
184         }
185
186         void IAnimatable.BatchCommit()
187         {
188         }
189
190         protected override IntPtr CreateHandle(EvasObject parent)
191         {
192             _mainLayout = new Box(parent);
193             return _mainLayout.Handle;
194         }
195
196         void Initialize()
197         {
198             _mainLayout.SetLayoutCallback(OnLayout);
199
200             _contentGestureBox = new Box(_mainLayout);
201             _contentGestureBox.Show();
202             _mainLayout.PackEnd(_contentGestureBox);
203
204             _contentBox = new Box(_mainLayout);
205             _contentBox.SetLayoutCallback(OnContentLayout);
206             _contentBox.Show();
207             _mainLayout.PackEnd(_contentBox);
208         }
209
210         void InitializeDrawerBox()
211         {
212             if (_drawerBox != null)
213                 return;
214
215             _drawerBox = new Box(_mainLayout);
216             _drawerBox.SetLayoutCallback(OnDrawerLayout);
217             _drawerBox.Show();
218             _mainLayout.PackEnd(_drawerBox);
219
220             _drawerContentBox = new Box(_drawerBox);
221             _drawerBox.PackEnd(_drawerContentBox);
222
223             _drawerIconBox = new Box(_drawerBox)
224             {
225                 BackgroundColor = _handlerBackgroundColor
226             };
227             _drawerBox.PackEnd(_drawerIconBox);
228
229             _drawerIcon = new EImage(_drawerIconBox)
230             {
231                 AlignmentY = 0.5,
232                 AlignmentX = 0.5,
233                 MinimumHeight = IconSize,
234                 MinimumWidth = IconSize,
235             };
236             _drawerIcon.Show();
237             _drawerIconBox.PackEnd(_drawerIcon);
238             SetDrawerIcon(_drawerIconSource);
239
240             _touchArea = new EButton(_drawerBox)
241             {
242                 Color = EColor.Transparent,
243                 BackgroundColor = EColor.Transparent,
244             };
245             _touchArea.SetPartColor("effect", EColor.Transparent);
246             _touchArea.Show();
247             _touchArea.RepeatEvents = true;
248             _touchArea.Clicked += OnIconClicked;
249
250             _drawerBox.PackEnd(_touchArea);
251
252             _gestureOnContent = new GestureLayer(_contentGestureBox);
253             _gestureOnContent.SetMomentumCallback(GestureLayer.GestureState.Start, OnContentDragStarted);
254             _gestureOnContent.SetMomentumCallback(GestureLayer.GestureState.End, OnContentDragEnded);
255             _gestureOnContent.SetMomentumCallback(GestureLayer.GestureState.Abort, OnContentDragEnded);
256             _gestureOnContent.Attach(_contentGestureBox);
257             _contentBox.RepeatEvents = true;
258
259             _gestureOnDrawer = new GestureLayer(_drawerIconBox);
260             _gestureOnDrawer.SetMomentumCallback(GestureLayer.GestureState.Move, OnDrawerDragged);
261             _gestureOnDrawer.SetMomentumCallback(GestureLayer.GestureState.End, OnDrawerDragEnded);
262             _gestureOnDrawer.SetMomentumCallback(GestureLayer.GestureState.Abort, OnDrawerDragEnded);
263             _gestureOnDrawer.Attach(_drawerIconBox);
264
265             RotaryEventManager.Rotated += OnRotateEventReceived;
266         }
267
268         void SetDrawerIcon(ImageSource source)
269         {
270             if (source == null)
271             {
272                 _drawerIcon.LoadFromImageSourceAsync(ImageSource.FromResource(DefaultIcon, GetType().Assembly));
273                 _isDefaultIcon = true;
274             }
275             else
276             {
277                 _isDefaultIcon = false;
278                 if (source is FileImageSource fsource)
279                 {
280                     _drawerIcon.Load(fsource.ToAbsPath());
281                 }
282                 else
283                 {
284                     _drawerIcon.LoadFromImageSourceAsync(source);
285                 }
286             }
287         }
288
289         void UpdateHandlerBackgroundColor()
290         {
291             if (_drawerIconBox != null)
292             {
293                 _drawerIconBox.BackgroundColor = _handlerBackgroundColor;
294             }
295         }
296
297         void OnIconClicked(object sender, EventArgs e)
298         {
299             if (IsOpen)
300                 Close();
301             else
302                 Open();
303         }
304
305         async Task<bool> ShowAsync(EWidget target, Easing easing = null, uint length = 300, CancellationToken cancelltaionToken = default(CancellationToken))
306         {
307             var tcs = new TaskCompletionSource<bool>();
308
309             await Task.Delay(1000);
310
311             if (cancelltaionToken.IsCancellationRequested)
312             {
313                 cancelltaionToken.ThrowIfCancellationRequested();
314             }
315
316             target.Show();
317             var opacity = target.Opacity;
318
319             if (opacity == 255 || opacity == -1)
320                 return true;
321
322             new Animation((progress) =>
323             {
324                 target.Opacity = opacity + (int)((255 - opacity) * progress);
325
326             }).Commit(this, "FadeIn", length: length, finished: (p, e) =>
327             {
328                 target.Opacity = 255;
329                 tcs.SetResult(true);
330                 StartHighlightAnimation(_drawerIcon);
331             });
332
333             return await tcs.Task;
334         }
335
336         void OnLayout()
337         {
338             var bound = Geometry;
339             _contentGestureBox.Geometry = bound;
340             _contentBox.Geometry = bound;
341             if (_drawerBox != null)
342             {
343                 bound.Y = _isOpen ? 0 : (bound.Height - HandlerHeight);
344                 _drawerBox.Geometry = bound;
345             }
346         }
347
348         void OnContentLayout()
349         {
350             if (_content != null)
351             {
352                 _content.Geometry = _contentBox.Geometry;
353             }
354         }
355
356         void OnDrawerLayout()
357         {
358             this.AbortAnimation("HighlightAnimation");
359
360             var bound = _drawerBox.Geometry;
361
362             var currentY = bound.Y;
363             var ratio = currentY / (double)(Geometry.Height - HandlerHeight);
364
365             var contentBound = bound;
366             contentBound.Y += (int)(HandlerHeight * ratio);
367             _drawerContentBox.Geometry = contentBound;
368
369             var drawerHandleBound = bound;
370             drawerHandleBound.Height = HandlerHeight;
371             _drawerIconBox.Geometry = drawerHandleBound;
372
373             var drawerTouchBound = drawerHandleBound;
374             drawerTouchBound.Width = TouchWidth;
375             drawerTouchBound.X = drawerHandleBound.X + (drawerHandleBound.Width - TouchWidth) / 2;
376             _touchArea.Geometry = drawerTouchBound;
377         }
378
379         async Task<bool> HideAsync(EWidget target, Easing easing = null, uint length = 300)
380         {
381             var tcs = new TaskCompletionSource<bool>();
382
383             var opacity = target.Opacity;
384             if (opacity == -1)
385                 opacity = 255;
386
387             new Animation((progress) =>
388             {
389                 target.Opacity = opacity - (int)(progress * opacity);
390
391             }).Commit(this, "FadeOut", length: length, finished: (p, e) =>
392             {
393                 target.Opacity = 0;
394                 target.Hide();
395                 tcs.SetResult(true);
396             });
397
398             return await tcs.Task;
399         }
400
401         void StartHighlightAnimation(EWidget target)
402         {
403             if (!_isDefaultIcon || this.AnimationIsRunning("HighlightAnimation"))
404                 return;
405
406             int count = 2;
407             var bound = target.Geometry;
408             var y = bound.Y;
409             var dy = bound.Y - bound.Height / 3;
410
411             var anim = new Animation();
412
413             var transfAnim = new Animation((f) =>
414             {
415                 bound.Y = (int)f;
416                 var map = new EvasMap(4);
417                 map.PopulatePoints(bound, 0);
418                 target.IsMapEnabled = true;
419                 target.EvasMap = map;
420             }, y, dy);
421
422             var opacityAnim = new Animation(f => target.Opacity = (int)f, 255, 40);
423
424             anim.Add(0, 1, opacityAnim);
425             anim.Add(0, 1, transfAnim);
426
427             anim.Commit(this, "HighlightAnimation", 16, 800, finished: (f, b) =>
428             {
429                 target.Opacity = 255;
430                 target.IsMapEnabled = false;
431             }, repeat:() => --count > 0);
432         }
433
434         async void OnRotateEventReceived(EventArgs args)
435         {
436             _fadeInCancelTokenSource?.Cancel();
437             _fadeInCancelTokenSource = new CancellationTokenSource();
438
439             if (!_isOpen)
440             {
441                 var token = _fadeInCancelTokenSource.Token;
442                 await HideAsync(_drawerBox);
443                 _ = ShowAsync(_drawerBox, cancelltaionToken: token);
444             }
445         }
446
447         void OnContentDragStarted(GestureLayer.MomentumData moment)
448         {
449             _fadeInCancelTokenSource?.Cancel();
450             _fadeInCancelTokenSource = null;
451
452             if (!_isOpen)
453             {
454                 _ = HideAsync(_drawerBox);
455             }
456         }
457
458         void OnContentDragEnded(GestureLayer.MomentumData moment)
459         {
460             _fadeInCancelTokenSource = new CancellationTokenSource();
461             _ = ShowAsync(_drawerBox, cancelltaionToken: _fadeInCancelTokenSource.Token);
462         }
463
464         void OnDrawerDragged(GestureLayer.MomentumData moment)
465         {
466             var toMove = _drawerBox.Geometry;
467             toMove.Y = (moment.Y2 < 0) ? 0 : moment.Y2;
468             _drawerBox.Geometry = toMove;
469             OnDrawerLayout();
470         }
471
472         void OnDrawerDragEnded(GestureLayer.MomentumData moment)
473         {
474             if (_drawerBox.Geometry.Y < (_mainLayout.Geometry.Height / 2))
475             {
476                 Open();
477             }
478             else
479             {
480                 Close();
481             }
482         }
483
484         void FlipIcon()
485         {
486             if (_isDefaultIcon)
487             {
488                 _drawerIcon.Orientation = ImageOrientation.FlipVertical;
489             }
490         }
491
492         void ResetIcon()
493         {
494             _drawerIcon.Orientation = ImageOrientation.None;
495         }
496
497         Task RunMoveAnimation(EvasObject target, Rect dest, uint length, Easing easing = null)
498         {
499             var tcs = new TaskCompletionSource<bool>();
500
501             var dx = target.Geometry.X - dest.X;
502             var dy = target.Geometry.Y - dest.Y;
503
504             new Animation((progress) =>
505             {
506                 var toMove = dest;
507                 toMove.X += (int)(dx * (1 - progress));
508                 toMove.Y += (int)(dy * (1 - progress));
509                 target.Geometry = toMove;
510                 OnDrawerLayout();
511             }).Commit(this, "Move", length: length, finished: (s, e) =>
512             {
513                 target.Geometry = dest;
514                 tcs.SetResult(true);
515             });
516             return tcs.Task;
517         }
518
519         void UnsetMainContent()
520         {
521             if (_content != null)
522             {
523                 _contentBox.UnPack(_content);
524                 _content.Hide();
525                 _content = null;
526             }
527         }
528
529         void UnsetDrawerContent()
530         {
531             if (_drawerContent != null)
532             {
533                 _drawerContentBox.UnPack(_drawerContent);
534                 _drawerContent.Hide();
535                 _drawerContent = null;
536
537                 _drawerContentBox.Hide();
538                 _drawerIconBox.Hide();
539             }
540         }
541     }
542 }