[NUI] Minimize size is set too large. and code clean
[platform/core/csapi/tizenfx.git] / src / Tizen.NUI / src / public / Window / DefaultBorder.cs
1 /*
2  * Copyright(c) 2022 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.ComponentModel;
20 using Tizen.NUI.BaseComponents;
21
22 namespace Tizen.NUI
23 {
24     /// <summary>
25     /// This class creates a border UI.
26     /// </summary>
27     [EditorBrowsable(EditorBrowsableState.Never)]
28     public class DefaultBorder : IBorderInterface
29     {
30         #region Constant Fields
31         private static readonly string ResourcePath = FrameworkInformation.ResourcePath;
32         private static readonly string MinimalizeIcon = ResourcePath + "minimalize.png";
33         private static readonly string MaximalizeIcon = ResourcePath + "maximalize.png";
34         private static readonly string CloseIcon = ResourcePath + "close.png";
35         private static readonly string LeftCornerIcon = ResourcePath + "leftCorner.png";
36         private static readonly string RightCornerIcon = ResourcePath + "rightCorner.png";
37
38         private static readonly string DarkMinimalizeIcon = ResourcePath + "dark_minimalize.png";
39         private static readonly string DarkPreviousIcon = ResourcePath + "dark_smallwindow.png";
40         private static readonly string DarkCloseIcon = ResourcePath + "dark_close.png";
41         private static readonly string DarkLeftCornerIcon = ResourcePath + "dark_leftCorner.png";
42         private static readonly string DarkRightCornerIcon = ResourcePath + "dark_rightCorner.png";
43
44
45         private const uint DefaultHeight = 50;
46         private const uint DefaultLineThickness = 5;
47         private const uint DefaultTouchThickness = 20;
48         private static readonly Color DefaultBackgroundColor = new Color(1, 1, 1, 0.3f);
49         private static readonly Color DefaultClickedBackgroundColor = new Color(1, 1, 1, 0.4f);
50         private static readonly Size2D DefaultMinSize = new Size2D(100, 0);
51         #endregion //Constant Fields
52
53
54         #region Fields
55         private Color backgroundColor;
56         private View rootView;
57         private View borderView;
58
59         private ImageView minimalizeIcon;
60         private ImageView maximalizeIcon;
61         private ImageView closeIcon;
62         private ImageView leftCornerIcon;
63         private ImageView rightCornerIcon;
64
65         private Window.BorderDirection direction = Window.BorderDirection.None;
66         private float preScale = 0;
67
68         private View windowView = null;
69         private bool isWinGestures = false;
70         private Timer timer;
71
72         private CurrentGesture currentGesture = CurrentGesture.None;
73         private bool disposed = false;
74         #endregion //Fields
75
76         #region Events
77         private PanGestureDetector borderPanGestureDetector;
78         private PinchGestureDetector borderPinchGestureDetector;
79         private PanGestureDetector winPanGestureDetector;
80         private TapGestureDetector winTapGestureDetector;
81         #endregion //Events
82
83         #region Enums
84         private enum CurrentGesture
85         {
86           None = 0,
87           TapGesture = 1,
88           PanGesture = 2,
89           PinchGesture = 3,
90         }
91         #endregion //Enums
92
93         #region Methods
94
95         /// <summary>
96         /// The thickness of the border.
97         /// </summary>
98         [EditorBrowsable(EditorBrowsableState.Never)]
99         public uint BorderLineThickness {get; set;}
100
101         /// <summary>
102         /// The thickness of the border's touch area.
103         /// </summary>
104         [EditorBrowsable(EditorBrowsableState.Never)]
105         public uint TouchThickness {get; set;}
106
107         /// <summary>
108         /// The height of the border.
109         /// </summary>
110         [EditorBrowsable(EditorBrowsableState.Never)]
111         public uint BorderHeight {get; set;}
112
113         /// <summary>
114         /// The minimum size by which the window will small.
115         /// </summary>
116         [EditorBrowsable(EditorBrowsableState.Never)]
117         public Size2D MinSize {get; set;}
118
119         /// <summary>
120         /// The maximum size by which the window will big.
121         /// </summary>
122         [EditorBrowsable(EditorBrowsableState.Never)]
123         public Size2D MaxSize {get; set;}
124
125         /// <summary>
126         /// The window with borders added.
127         /// </summary>
128         [EditorBrowsable(EditorBrowsableState.Never)]
129         public Window BorderWindow {get; set;}
130
131         /// <summary>
132         /// Whether overlay mode.
133         /// If overlay mode is true, the border area is hidden when the window is maximized.
134         /// And if you touched at screen, the border area is shown on the screen.
135         /// Default value is false;
136         /// </summary>
137         [EditorBrowsable(EditorBrowsableState.Never)]
138         public bool OverlayMode {get; set;}
139
140         [EditorBrowsable(EditorBrowsableState.Never)]
141         public DefaultBorder()
142         {
143             BorderLineThickness = DefaultLineThickness;
144             TouchThickness = DefaultTouchThickness;
145             BorderHeight = DefaultHeight;
146             MinSize = DefaultMinSize;
147             OverlayMode = false;
148         }
149
150
151         /// <summary>
152         /// Create border UI. Users can override this method to draw border UI.
153         /// </summary>
154         [EditorBrowsable(EditorBrowsableState.Never)]
155         public virtual void CreateBorderView(View rootView)
156         {
157             if (rootView == null)
158             {
159                 return;
160             }
161             this.rootView = rootView;
162             rootView.BackgroundColor = DefaultBackgroundColor;
163             rootView.CornerRadius = new Vector4(0.03f, 0.03f, 0.03f, 0.03f);
164             rootView.CornerRadiusPolicy = VisualTransformPolicyType.Relative;
165
166             borderView = new View()
167             {
168                 Layout = new LinearLayout()
169                 {
170                     LinearAlignment = LinearLayout.Alignment.End,
171                     LinearOrientation = LinearLayout.Orientation.Horizontal,
172                 },
173                 WidthSpecification = LayoutParamPolicies.MatchParent,
174                 HeightSpecification = LayoutParamPolicies.MatchParent,
175             };
176
177             minimalizeIcon = new ImageView()
178             {
179                 ResourceUrl = MinimalizeIcon,
180                 PositionUsesPivotPoint = true,
181                 PivotPoint = PivotPoint.BottomLeft,
182                 ParentOrigin = ParentOrigin.BottomLeft,
183             };
184
185             maximalizeIcon = new ImageView()
186             {
187                 ResourceUrl = MaximalizeIcon,
188                 PositionUsesPivotPoint = true,
189                 PivotPoint = PivotPoint.BottomLeft,
190                 ParentOrigin = ParentOrigin.BottomLeft,
191             };
192
193             closeIcon = new ImageView()
194             {
195                 ResourceUrl = CloseIcon,
196                 PositionUsesPivotPoint = true,
197                 PivotPoint = PivotPoint.BottomLeft,
198                 ParentOrigin = ParentOrigin.BottomLeft,
199             };
200
201             leftCornerIcon = new ImageView()
202             {
203                 ResourceUrl = LeftCornerIcon,
204                 PositionUsesPivotPoint = true,
205                 PivotPoint = PivotPoint.BottomLeft,
206                 ParentOrigin = ParentOrigin.BottomLeft,
207             };
208
209             rightCornerIcon = new ImageView()
210             {
211               ResourceUrl = RightCornerIcon,
212               PositionUsesPivotPoint = true,
213               PivotPoint = PivotPoint.BottomLeft,
214               ParentOrigin = ParentOrigin.BottomLeft,
215             };
216
217             rootView.Add(leftCornerIcon);
218             borderView.Add(minimalizeIcon);
219             borderView.Add(maximalizeIcon);
220             borderView.Add(closeIcon);
221             borderView.Add(rightCornerIcon);
222             rootView.Add(borderView);
223
224             minimalizeIcon.TouchEvent += OnMinimizeIconTouched;
225             maximalizeIcon.TouchEvent += OnMaximizeIconTouched;
226             closeIcon.TouchEvent += OnCloseIconTouched;
227             leftCornerIcon.TouchEvent += OnLeftCornerIconTouched;
228             rightCornerIcon.TouchEvent += OnRightCornerIconTouched;
229         }
230
231         /// Determines the behavior of pinch gesture.
232         private void OnPinchGestureDetected(object source, PinchGestureDetector.DetectedEventArgs e)
233         {
234             if (e == null)
235             {
236                 return;
237             }
238             if (e.PinchGesture.State == Gesture.StateType.Started)
239             {
240                 preScale = e.PinchGesture.Scale;
241             }
242             else if (e.PinchGesture.State == Gesture.StateType.Finished || e.PinchGesture.State == Gesture.StateType.Cancelled)
243             {
244                 if (preScale > e.PinchGesture.Scale)
245                 {
246                     if (BorderWindow.IsMaximized())
247                     {
248                         BorderWindow.Maximize(false);
249                     }
250                     else
251                     {
252                         BorderWindow.Minimize(true);
253                     }
254                 }
255                 else
256                 {
257                     BorderWindow.Maximize(true);
258                 }
259             }
260         }
261
262         /// Determines the behavior of borders.
263         private void OnPanGestureDetected(object source, PanGestureDetector.DetectedEventArgs e)
264         {
265             if (e == null)
266             {
267                 return;
268             }
269             PanGesture panGesture = e.PanGesture;
270
271             if (panGesture.State == Gesture.StateType.Started)
272             {
273                 direction = BorderWindow.GetDirection(panGesture.Position.X, panGesture.Position.Y);
274                 if (direction == Window.BorderDirection.Move)
275                 {
276                     if (BorderWindow.IsMaximized() == true)
277                     {
278                         BorderWindow.Maximize(false);
279                     }
280                     else
281                     {
282                         BorderWindow.RequestMoveToServer();
283                     }
284                 }
285                 else if (direction != Window.BorderDirection.None)
286                 {
287                     OnRequestResize();
288                     BorderWindow.RequestResizeToServer((Window.ResizeDirection)direction);
289                 }
290             }
291             else if (panGesture.State == Gesture.StateType.Continuing)
292             {
293                 if (direction == Window.BorderDirection.BottomLeft || direction == Window.BorderDirection.BottomRight || direction == Window.BorderDirection.TopLeft || direction == Window.BorderDirection.TopRight)
294                 {
295                     BorderWindow.WindowSize += new Size2D((int)panGesture.ScreenDisplacement.X, (int)panGesture.ScreenDisplacement.Y);
296                 }
297                 else if (direction == Window.BorderDirection.Left || direction == Window.BorderDirection.Right)
298                 {
299                     BorderWindow.WindowSize += new Size2D((int)panGesture.ScreenDisplacement.X, 0);
300                 }
301                 else if (direction == Window.BorderDirection.Bottom || direction == Window.BorderDirection.Top)
302                 {
303                     BorderWindow.WindowSize += new Size2D(0, (int)panGesture.ScreenDisplacement.Y);
304                 }
305                 else if (direction == Window.BorderDirection.Move)
306                 {
307                     BorderWindow.WindowPosition += new Position2D((int)panGesture.ScreenDisplacement.X, (int)panGesture.ScreenDisplacement.Y);
308                 }
309             }
310             else if (panGesture.State == Gesture.StateType.Finished || panGesture.State == Gesture.StateType.Cancelled)
311             {
312                 direction = Window.BorderDirection.None;
313                 ClearWindowGesture();
314             }
315         }
316
317
318         /// <summary>
319         /// This is an event callback when the left corner icon is touched.
320         /// </summary>
321         [EditorBrowsable(EditorBrowsableState.Never)]
322         public virtual bool OnLeftCornerIconTouched(object sender, View.TouchEventArgs e)
323         {
324             if (e == null)
325             {
326                 return false;
327             }
328             if (e.Touch.GetState(0) == PointStateType.Down)
329             {
330               ClearWindowGesture();
331               OnRequestResize();
332               BorderWindow.RequestResizeToServer(Window.ResizeDirection.BottomLeft);
333             }
334             return true;
335         }
336
337         /// <summary>
338         ///This is an event callback when the right corner icon is touched.
339         /// </summary>
340         [EditorBrowsable(EditorBrowsableState.Never)]
341         public virtual bool OnRightCornerIconTouched(object sender, View.TouchEventArgs e)
342         {
343             if (e == null)
344             {
345                 return false;
346             }
347             if (e.Touch.GetState(0) == PointStateType.Down)
348             {
349               ClearWindowGesture();
350               OnRequestResize();
351               BorderWindow.RequestResizeToServer(Window.ResizeDirection.BottomRight);
352             }
353             return true;
354         }
355
356
357         /// <summary>
358         /// This is an event callback when the minimize button is touched.
359         /// </summary>
360         [EditorBrowsable(EditorBrowsableState.Never)]
361         public virtual bool OnMinimizeIconTouched(object sender, View.TouchEventArgs e)
362         {
363             if (e == null)
364             {
365                 return false;
366             }
367             if (e.Touch.GetState(0) == PointStateType.Up)
368             {
369                 ClearWindowGesture();
370                 BorderWindow.Minimize(true);
371             }
372             return true;
373         }
374
375         /// <summary>
376         /// This is an event callback when the maximum button is touched.
377         /// </summary>
378         [EditorBrowsable(EditorBrowsableState.Never)]
379         public virtual bool OnMaximizeIconTouched(object sender, View.TouchEventArgs e)
380         {
381             if (e == null)
382             {
383                 return false;
384             }
385             if (e.Touch.GetState(0) == PointStateType.Up)
386             {
387                 ClearWindowGesture();
388                 if (BorderWindow.IsMaximized())
389                 {
390                   BorderWindow.Maximize(false);
391                 }
392                 else
393                 {
394                   BorderWindow.Maximize(true);
395                 }
396             }
397             return true;
398         }
399
400         /// <summary>
401         /// This is an event callback when the close button is touched.
402         /// </summary>
403         [EditorBrowsable(EditorBrowsableState.Never)]
404         public virtual bool OnCloseIconTouched(object sender, View.TouchEventArgs e)
405         {
406             if (e == null)
407             {
408                 return false;
409             }
410             if (e.Touch.GetState(0) == PointStateType.Up)
411             {
412                 BorderWindow.Destroy();
413                 BorderWindow = null;
414             }
415             return true;
416         }
417
418
419         private void UpdateIcons()
420         {
421             if (BorderWindow != null && rootView != null)
422             {
423                 if (BorderWindow.IsMaximized() == true)
424                 {
425                     if (maximalizeIcon != null)
426                     {
427                         maximalizeIcon.ResourceUrl = DarkPreviousIcon;
428                     }
429                     if (minimalizeIcon != null)
430                     {
431                         minimalizeIcon.ResourceUrl = DarkMinimalizeIcon;
432                     }
433                     if (closeIcon != null)
434                     {
435                         closeIcon.ResourceUrl = DarkCloseIcon;
436                     }
437                     if (leftCornerIcon != null)
438                     {
439                         leftCornerIcon.ResourceUrl = DarkLeftCornerIcon;
440                     }
441                     if (rightCornerIcon != null)
442                     {
443                         rightCornerIcon.ResourceUrl = DarkRightCornerIcon;
444                     }
445                     rootView.CornerRadius = new Vector4(0, 0, 0, 0);
446                     rootView.CornerRadiusPolicy = VisualTransformPolicyType.Relative;
447                     BorderWindow.SetTransparency(false);
448                 }
449                 else
450                 {
451                     if (maximalizeIcon != null)
452                     {
453                         maximalizeIcon.ResourceUrl = MaximalizeIcon;
454                     }
455                     if (minimalizeIcon != null)
456                     {
457                         minimalizeIcon.ResourceUrl = MinimalizeIcon;
458                     }
459                     if (closeIcon != null)
460                     {
461                         closeIcon.ResourceUrl = CloseIcon;
462                     }
463                     if (leftCornerIcon != null)
464                     {
465                         leftCornerIcon.ResourceUrl = LeftCornerIcon;
466                     }
467                     if (rightCornerIcon != null)
468                     {
469                         rightCornerIcon.ResourceUrl = RightCornerIcon;
470                     }
471                     rootView.CornerRadius = new Vector4(0.03f, 0.03f, 0.03f, 0.03f);
472                     rootView.CornerRadiusPolicy = VisualTransformPolicyType.Relative;
473                     BorderWindow.SetTransparency(true);
474                 }
475             }
476         }
477
478
479         /// <summary>
480         /// Called after the border UI is created.
481         /// </summary>
482         /// <param name="rootView">The root view on which the border.</param>
483         [EditorBrowsable(EditorBrowsableState.Never)]
484         public virtual void OnCreated(View rootView)
485         {
486             if (rootView == null)
487             {
488                 return;
489             }
490             // Register to resize and move through pan gestures.
491             borderPanGestureDetector = new PanGestureDetector();
492             borderPanGestureDetector.Attach(rootView);
493             borderPanGestureDetector.Detected += OnPanGestureDetected;
494
495             // Register touch event for effect when border is touched.
496             rootView.LeaveRequired = true;
497             rootView.TouchEvent += (s, e) =>
498             {
499                 if (e.Touch.GetState(0) == PointStateType.Started)
500                 {
501                     backgroundColor = new Color(rootView.BackgroundColor);
502                     rootView.BackgroundColor = DefaultClickedBackgroundColor;
503                 }
504                 else if (e.Touch.GetState(0) == PointStateType.Finished ||
505                          e.Touch.GetState(0) == PointStateType.Leave ||
506                          e.Touch.GetState(0) == PointStateType.Interrupted)
507                 {
508                     rootView.BackgroundColor = backgroundColor;
509                 }
510                 return true;
511             };
512
513             borderPinchGestureDetector = new PinchGestureDetector();
514             borderPinchGestureDetector.Attach(rootView);
515             borderPinchGestureDetector.Detected += OnPinchGestureDetected;
516
517             AddInterceptGesture();
518         }
519
520
521         // Register an intercept touch event on the window.
522         private void AddInterceptGesture()
523         {
524             isWinGestures = false;
525             BorderWindow.InterceptTouchEvent += OnWinInterceptedTouch;
526         }
527
528         // Intercept touch on window.
529         private bool OnWinInterceptedTouch(object sender, Window.TouchEventArgs e)
530         {
531             if (e.Touch.GetState(0) == PointStateType.Stationary && e.Touch.GetPointCount() == 2)
532             {
533                 if (isWinGestures == false && timer == null)
534                 {
535                     timer = new Timer(300);
536                     timer.Tick += OnTick;
537                     timer.Start();
538                 }
539             }
540             else
541             {
542                 currentGesture = CurrentGesture.None;
543                 if (timer != null)
544                 {
545                     timer.Stop();
546                     timer.Dispose();
547                     timer = null;
548                 }
549             }
550             return false;
551         }
552
553         // If two finger long press is done, create a windowView.
554         // then, Register a gesture on the windowView to do a resize or move.
555         private bool OnTick(object o, Timer.TickEventArgs e)
556         {
557             windowView = new View()
558             {
559                 WidthResizePolicy = ResizePolicyType.FillToParent,
560                 HeightResizePolicy = ResizePolicyType.FillToParent,
561                 BackgroundColor = new Color(1, 1, 1, 0.5f),
562             };
563             windowView.TouchEvent += (s, e) =>
564             {
565                 return true;
566             };
567             BorderWindow.Add(windowView);
568
569             winTapGestureDetector = new TapGestureDetector();
570             winTapGestureDetector.Attach(windowView);
571             winTapGestureDetector.SetMaximumTapsRequired(3);
572             winTapGestureDetector.Detected += OnWinTapGestureDetected;
573
574             winPanGestureDetector = new PanGestureDetector();
575             winPanGestureDetector.Attach(windowView);
576             winPanGestureDetector.Detected += OnWinPanGestureDetected;
577
578             BorderWindow.InterceptTouchEvent -= OnWinInterceptedTouch;
579             isWinGestures = true;
580             return false;
581         }
582
583         // Behavior when the window is tapped.
584         private void OnWinTapGestureDetected(object source, TapGestureDetector.DetectedEventArgs e)
585         {
586           if (currentGesture <= CurrentGesture.TapGesture)
587           {
588               currentGesture = CurrentGesture.TapGesture;
589               if (e.TapGesture.NumberOfTaps == 2)
590               {
591                   if (BorderWindow.IsMaximized() == false)
592                   {
593                     BorderWindow.Maximize(true);
594                   }
595                   else
596                   {
597                     BorderWindow.Maximize(false);
598                   }
599               }
600               else
601               {
602                   ClearWindowGesture();
603               }
604           }
605         }
606
607         // Window moves through pan gestures.
608         private void OnWinPanGestureDetected(object source, PanGestureDetector.DetectedEventArgs e)
609         {
610             if (currentGesture <= CurrentGesture.PanGesture /*&& panGesture.NumberOfTouches == 1*/)
611             {
612                 PanGesture panGesture = e.PanGesture;
613
614                 if (panGesture.State == Gesture.StateType.Started)
615                 {
616                     currentGesture = CurrentGesture.PanGesture;
617                     if (BorderWindow.IsMaximized() == true)
618                     {
619                         BorderWindow.Maximize(false);
620                     }
621                     else
622                     {
623                         BorderWindow.RequestMoveToServer();
624                     }
625                 }
626                 else if (panGesture.State == Gesture.StateType.Finished || panGesture.State == Gesture.StateType.Cancelled)
627                 {
628                     currentGesture = CurrentGesture.None;
629                     ClearWindowGesture();
630                 }
631             }
632         }
633
634         private void ClearWindowGesture()
635         {
636             if (isWinGestures)
637             {
638                 winPanGestureDetector.Dispose();
639                 winTapGestureDetector.Dispose();
640
641                 isWinGestures = false;
642                 BorderWindow.Remove(windowView);
643                 BorderWindow.InterceptTouchEvent += OnWinInterceptedTouch;
644             }
645         }
646
647         /// <summary>
648         /// Called when requesting a resize
649         /// </summary>
650         [EditorBrowsable(EditorBrowsableState.Never)]
651         public virtual void OnRequestResize()
652         {
653         }
654
655         /// <summary>
656         /// Called when the window is resized.
657         /// </summary>
658         /// <param name="width">The width of the resized window</param>
659         /// <param name="height">The height of the resized window</param>
660         [EditorBrowsable(EditorBrowsableState.Never)]
661         public virtual void OnResized(int width, int height)
662         {
663             UpdateIcons();
664         }
665
666         [EditorBrowsable(EditorBrowsableState.Never)]
667         protected virtual void Dispose(bool disposing)
668         {
669             if (disposed)
670             {
671                 return;
672             }
673             if (disposing)
674             {
675                 ClearWindowGesture();
676                 if (BorderWindow != null)
677                 {
678                     BorderWindow.InterceptTouchEvent -= OnWinInterceptedTouch;
679                 }
680                 borderPanGestureDetector?.Dispose();
681                 borderPinchGestureDetector?.Dispose();
682                 backgroundColor?.Dispose();
683                 minimalizeIcon?.Dispose();
684                 maximalizeIcon?.Dispose();
685                 closeIcon?.Dispose();
686                 leftCornerIcon?.Dispose();
687                 rightCornerIcon?.Dispose();
688                 timer?.Dispose();
689                 windowView?.Dispose();
690                 borderView?.Dispose();
691                 rootView?.Dispose();
692             }
693             disposed = true;
694         }
695
696         /// <summary>
697         /// Dispose
698         /// </summary>
699         [EditorBrowsable(EditorBrowsableState.Never)]
700         public void Dispose()
701         {
702             Dispose(true);
703             global::System.GC.SuppressFinalize(this);
704         }
705         #endregion //Methods
706
707     }
708 }