87a0f2a4310691879ad0596deda5dc46bfd0d54e
[platform/core/csapi/tizenfx.git] / src / Tizen.NUI / src / public / Window / BorderWindow.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 extern alias TizenSystemInformation;
18 using TizenSystemInformation.Tizen.System;
19
20 using System;
21 using System.Collections.Generic;
22 using System.ComponentModel;
23 using System.Linq;
24 using System.Threading;
25 using Tizen.NUI.BaseComponents;
26
27 namespace Tizen.NUI
28 {
29     public partial class Window
30     {
31         #region Constant Fields
32         #endregion //Constant Fields
33
34         #region Fields
35         private IBorderInterface borderInterface = null;
36         private Layer borderWindowRootLayer = null;
37         private Layer borderWindowBottomLayer = null;
38         private bool isBorderWindow = false;
39
40         // for border area
41         private View rootView = null;
42         private BorderView borderView = null;
43         private View topView = null;
44         private View contentsView = null;
45         private View bottomView = null;
46         private bool isTop = false;
47         private bool isBottom = false;
48         private float borderHeight = 0;
49         private int screenWidth = 0;
50         private int screenHeight = 0;
51
52         // for config
53         private Size2D minSize = null;
54         private Size2D maxSize = null;
55         private BorderResizePolicyType borderResizePolicy = BorderResizePolicyType.Free;
56         #endregion //Fields
57
58         #region Constructors
59         #endregion //Constructors
60
61         #region Distructors
62         #endregion //Distructors
63
64         #region Delegates
65         internal delegate void BorderCloseDelegate();
66         private BorderCloseDelegate borderCloseDelegate = null;
67
68         #endregion //Delegates
69
70         #region Events
71         #endregion //Events
72
73         #region Enums
74         /// <summary>
75         /// This is an enum for the resize direction or move value when the border area is touched.
76         /// </summary>
77         [EditorBrowsable(EditorBrowsableState.Never)]
78         public enum BorderDirection
79         {
80             None        = ResizeDirection.None,
81             TopLeft     = ResizeDirection.TopLeft,
82             Top         = ResizeDirection.Top,
83             TopRight    = ResizeDirection.TopRight,
84             Left        = ResizeDirection.Left,
85             Right       = ResizeDirection.Right,
86             BottomLeft  = ResizeDirection.BottomLeft,
87             Bottom      = ResizeDirection.Bottom,
88             BottomRight = ResizeDirection.BottomRight,
89             Move,
90         }
91
92         /// <summary>
93         /// This enum is the policy when resizing the border window.
94         /// </summary>
95         [EditorBrowsable(EditorBrowsableState.Never)]
96         public enum BorderResizePolicyType
97         {
98           /// <summary>
99           /// The window can be resized freely.
100           /// </summary>
101           Free = 0,
102           /// <summary>
103           /// The window is resized according to the ratio.
104           /// </summary>
105           KeepRatio = 1,
106           /// <summary>
107           /// The window is not resized and is fixed.
108           /// </summary>
109           Fixed = 2,
110         }
111         #endregion //Enums
112
113         #region Interfaces
114         #endregion //Interfaces
115
116         #region Properties
117         /// <summary>
118         /// Whether the border is enabled.
119         /// </summary>
120         [EditorBrowsable(EditorBrowsableState.Never)]
121         public bool IsBorderEnabled => isBorderWindow;
122         #endregion //Properties
123
124         #region Indexers
125         #endregion //Indexers
126
127         #region Methods
128
129         /// <summary>
130         /// Update BorderProperty
131         /// </summary>
132         internal void UpdateProperty()
133         {
134             if (borderInterface != null)
135             {
136                 float height = 0;
137                 if (isTop) height += topView.SizeHeight;
138                 if (isBottom) height += bottomView.SizeHeight;
139
140                 if (height != borderHeight)
141                 {
142                     float diff = height - borderHeight;
143                     borderHeight = height;
144                     WindowSize = new Size2D(WindowSize.Width, WindowSize.Height + (int)(diff));
145                 }
146
147                 if (minSize != borderInterface.MinSize)
148                 {
149                     using Size2D mimimumSize = new Size2D(borderInterface.MinSize.Width, borderInterface.MinSize.Height + (int)borderHeight);
150                     SetMimimumSize(mimimumSize);
151                     minSize = borderInterface.MinSize;
152                 }
153                 if (maxSize != borderInterface.MaxSize)
154                 {
155                     using Size2D maximumSize = new Size2D(borderInterface.MaxSize.Width, borderInterface.MaxSize.Height + (int)borderHeight);
156                     SetMaximumSize(maximumSize);
157                     maxSize = borderInterface.MaxSize;
158                 }
159                 if (borderResizePolicy != borderInterface.ResizePolicy)
160                 {
161                     AddAuxiliaryHint("wm.policy.win.resize_aspect_ratio", "0");
162                     borderResizePolicy = borderInterface.ResizePolicy;
163                     if (borderResizePolicy == BorderResizePolicyType.KeepRatio)
164                     {
165                         AddAuxiliaryHint("wm.policy.win.resize_aspect_ratio", "1");
166                     }
167                 }
168             }
169         }
170         /// <summary>
171         /// Called when the border is closed.
172         /// If the delegate is declared, the delegate is called, otherwise window is destroyed.
173         /// </summary>
174         internal void BorderDestroy()
175         {
176             if (borderCloseDelegate != null)
177             {
178                 borderCloseDelegate();
179             }
180             else
181             {
182                 Destroy();
183             }
184         }
185         /// <summary>
186         /// Enable the border window with IBorderInterface.
187         /// This adds a border area to the Window.
188         /// The border's UI is configured using IBorderInterface.
189         /// Users can reisze and move by touching the border area.
190         /// </summary>
191         /// <param name="borderInterface">The IBorderInterface.</param>
192         /// <param name="borderCloseDelegate">The BorderCloseDelegate. When close, this delegate is called.</param>
193         /// <returns>Whether the border window is enabled</returns>
194         internal bool EnableBorder(IBorderInterface borderInterface, BorderCloseDelegate borderCloseDelegate = null)
195         {
196             if (isBorderWindow == true)
197             {
198                 Tizen.Log.Error("NUI", $"Already EnableBorderWindow\n");
199                 return false;
200             }
201
202             try
203             {
204                 Information.TryGetValue<int>("http://tizen.org/feature/screen.width", out screenWidth);
205                 Information.TryGetValue<int>("http://tizen.org/feature/screen.height", out screenHeight);
206             }
207             catch (DllNotFoundException e)
208             {
209                 Tizen.Log.Fatal("NUI", $"{e}\n");
210             }
211
212             if (borderInterface == null)
213             {
214                 borderInterface = new DefaultBorder();
215             }
216             this.borderInterface = borderInterface;
217             this.borderCloseDelegate = borderCloseDelegate;
218
219             GetDefaultLayer().Name = "OriginalRootLayer";
220
221             SetTransparency(true);
222             BackgroundColor = Color.Transparent;
223             borderInterface.BorderWindow = this;
224
225             if (CreateBorder() == true)
226             {
227                 using var realWindowSize = new Size2D(WindowSize.Width, WindowSize.Height);
228
229                 isBorderWindow = true;
230
231                 Resized += OnBorderWindowResized;
232
233                 Moved += OnBorderWindowMoved;
234
235                 borderInterface.OnCreated(borderView);
236
237                 // Increase the window size as much as the border area.
238                 borderHeight = 0;
239                 if (isTop) borderHeight += topView.SizeHeight;
240                 if (isBottom) borderHeight += bottomView.SizeHeight;
241
242                 // Sets the minimum / maximum size to be resized by RequestResizeToServer.
243                 if (borderInterface.MinSize != null)
244                 {
245                     using Size2D mimimumSize = new Size2D(borderInterface.MinSize.Width, borderInterface.MinSize.Height + (int)borderHeight);
246                     SetMimimumSize(mimimumSize);
247                 }
248                 if (borderInterface.MaxSize != null)
249                 {
250                     using Size2D maximumSize = new Size2D(borderInterface.MaxSize.Width, borderInterface.MaxSize.Height + (int)borderHeight);
251                     SetMaximumSize(maximumSize);
252                 }
253
254                 // When running the app for the first time, if it runs in full size, do Maximize(true).
255                 if (screenWidth != 0 && screenHeight != 0 &&
256                     realWindowSize.Width >= screenWidth && realWindowSize.Height >= screenHeight &&
257                     IsMaximized() == false)
258                 {
259                     Maximize(true);
260                     borderInterface.OnMaximize(true);
261                     ResizedEventArgs e = new ResizedEventArgs();
262                     e.WindowSize = WindowSize;
263                     OnBorderWindowResized(this, e);
264                 }
265                 else
266                 {
267                     WindowSize += new Size2D((int)borderInterface.BorderLineThickness * 2, (int)(borderHeight + borderInterface.BorderLineThickness * 2));
268                 }
269
270                 // If it is BorderResizePolicyType.KeepRatio type, it will be resized according to the ratio.
271                 if (borderInterface.ResizePolicy == BorderResizePolicyType.KeepRatio)
272                 {
273                     AddAuxiliaryHint("wm.policy.win.resize_aspect_ratio", "1");
274                 }
275
276                 // Add a view to the border layer.
277                 GetBorderWindowBottomLayer().Add(rootView);
278
279                 FocusChanged += OnWindowFocusChanged;
280
281                 return true;
282             }
283             else
284             {
285                 this.borderInterface.Dispose();
286                 return false;
287             }
288         }
289
290         private void OnWindowFocusChanged(object sender, Window.FocusChangedEventArgs e)
291         {
292             if (e.FocusGained == true && IsMaximized() == false)
293             {
294                 // Raises the window when the window is focused.
295                 Raise();
296             }
297         }
298
299         /// Create the border UI.
300         private bool CreateBorder()
301         {
302             rootView = new View()
303             {
304                 WidthResizePolicy = ResizePolicyType.FillToParent,
305                 HeightResizePolicy = ResizePolicyType.FillToParent,
306                 BackgroundColor = Color.Transparent,
307             };
308
309             ushort padding = (ushort) borderInterface.BorderLineThickness;
310             borderView = new BorderView()
311             {
312                 GrabTouchAfterLeave = true,
313                 WidthResizePolicy = ResizePolicyType.FillToParent,
314                 HeightResizePolicy = ResizePolicyType.FillToParent,
315                 BackgroundColor = Color.Transparent,
316                 Layout = new LinearLayout() {
317                     LinearOrientation = LinearLayout.Orientation.Vertical,
318                     LinearAlignment = LinearLayout.Alignment.Top
319                 },
320                 Padding = new Extents(padding, padding, padding, padding),
321             };
322             borderInterface.CreateBorderView(borderView);
323
324             topView = new View()
325             {
326                 WidthSpecification = LayoutParamPolicies.MatchParent,
327                 SizeHeight = borderInterface.BorderHeight,
328                 BackgroundColor = Color.Transparent,
329             };
330
331             contentsView = new View()
332             {
333                 BackgroundColor = Color.Transparent,
334                 WidthSpecification = LayoutParamPolicies.MatchParent,
335             };
336
337             bottomView = new View()
338             {
339                 WidthSpecification = LayoutParamPolicies.MatchParent,
340                 SizeHeight = borderInterface.BorderHeight,
341                 BackgroundColor = Color.Transparent,
342             };
343
344             // // Gets the Border's UI.
345             if (borderInterface.CreateTopBorderView(topView) == true && topView != null)
346             {
347                 borderView.Add(topView);
348                 isTop = true;
349             }
350             borderView.Add(contentsView);
351             if (borderInterface.CreateBottomBorderView(bottomView) == true && bottomView != null)
352             {
353                 borderView.Add(bottomView);
354                 isBottom = true;
355             }
356             rootView.Add(borderView);
357
358             return isTop || isBottom;
359         }
360
361         /// <summary>
362         /// Calculates which direction to resize or to move.
363         /// </summary>
364         /// <param name="xPosition">The X position.</param>
365         /// <param name="yPosition">The Y position.</param>
366         /// <returns>The BorderDirection</returns>
367         [EditorBrowsable(EditorBrowsableState.Never)]
368         public BorderDirection GetDirection(float xPosition, float yPosition)
369         {
370             BorderDirection direction = BorderDirection.None;
371
372             // check bottom left corner
373             if (xPosition < borderInterface.TouchThickness && yPosition > WindowSize.Height + borderHeight - borderInterface.TouchThickness)
374             {
375                 direction = BorderDirection.BottomLeft;
376             }
377             // check bottom right corner
378             else if (xPosition > WindowSize.Width + (float)(borderInterface.BorderLineThickness * 2) - borderInterface.TouchThickness && yPosition > WindowSize.Height + borderHeight - borderInterface.TouchThickness)
379             {
380                 direction = BorderDirection.BottomRight;
381             }
382             // check top left corner
383             else if (xPosition < borderInterface.TouchThickness && yPosition <  borderInterface.TouchThickness)
384             {
385                 direction = BorderDirection.TopLeft;
386             }
387             // check top right corner
388             else if (xPosition > WindowSize.Width + (float)(borderInterface.BorderLineThickness * 2) - borderInterface.TouchThickness && yPosition < borderInterface.TouchThickness)
389             {
390                 direction = BorderDirection.TopRight;
391             }
392             // check left side
393             else if (xPosition < borderInterface.TouchThickness)
394             {
395                 direction = BorderDirection.Left;
396             }
397             // check right side
398             else if (xPosition > WindowSize.Width + (float)(borderInterface.BorderLineThickness * 2) - borderInterface.TouchThickness)
399             {
400                 direction = BorderDirection.Right;
401             }
402             // check bottom side
403             else if (yPosition > WindowSize.Height + borderHeight + borderInterface.BorderLineThickness - borderInterface.TouchThickness)
404             {
405                 direction = BorderDirection.Bottom;
406             }
407             // check top side
408             else if (yPosition < borderInterface.TouchThickness)
409             {
410                 direction = BorderDirection.Top;
411             }
412             // check move
413             else if ((yPosition > WindowSize.Height) || (isTop == true && yPosition < topView.SizeHeight))
414             {
415                 direction = BorderDirection.Move;
416             }
417
418             return direction;
419         }
420
421         private void DoOverlayMode(bool enable)
422         {
423             if (borderInterface.OverlayMode == true)
424             {
425                 borderInterface.OnOverlayMode(enable);
426                 borderView?.OverlayMode(enable);
427                 if (enable == true)
428                 {
429                     GetBorderWindowBottomLayer().RaiseToTop();
430                 }
431                 else
432                 {
433                     GetBorderWindowBottomLayer().LowerToBottom();
434                 }
435             }
436         }
437
438         // Called when the window position has changed.
439         private void OnBorderWindowMoved(object sender, WindowMovedEventArgs e)
440         {
441             Tizen.Log.Info("NUI", $"OnBorderWindowMoved {e.WindowPosition.X}, {e.WindowPosition.Y}\n");
442             borderInterface.OnMoved(e.WindowPosition.X, e.WindowPosition.X);
443         }
444
445
446         // Called when the window size has changed.
447         private void OnBorderWindowResized(object sender, Window.ResizedEventArgs e)
448         {
449             Tizen.Log.Info("NUI", $"OnBorderWindowResized {e.WindowSize.Width},{e.WindowSize.Height}\n");
450             int resizeWidth = e.WindowSize.Width;
451             int resizeHeight = e.WindowSize.Height;
452
453             borderInterface.OnResized(resizeWidth, resizeHeight);
454
455              // reset borderHeight
456             borderHeight = 0;
457             if (isTop) borderHeight += topView.SizeHeight;
458             if (isBottom) borderHeight += bottomView.SizeHeight;
459
460             if (borderInterface.OverlayMode == true && IsMaximized() == true)
461             {
462                 Interop.ActorInternal.SetSize(GetBorderWindowRootLayer().SwigCPtr, resizeWidth, resizeHeight);
463                 Interop.ActorInternal.SetSize(GetBorderWindowBottomLayer().SwigCPtr, resizeWidth, resizeHeight);
464                 Interop.ActorInternal.SetPosition(GetBorderWindowRootLayer().SwigCPtr, 0, 0);
465                 if (contentsView != null)
466                 {
467                     contentsView.SizeHeight = resizeHeight - borderHeight - (float)(borderInterface.BorderLineThickness * 2);
468                 }
469                 DoOverlayMode(true);
470             }
471             else
472             {
473                 float height = (isTop == true) ? topView.SizeHeight : 0;
474                 Interop.ActorInternal.SetSize(GetBorderWindowRootLayer().SwigCPtr, resizeWidth, resizeHeight);
475                 Interop.ActorInternal.SetSize(GetBorderWindowBottomLayer().SwigCPtr, resizeWidth + (float)(borderInterface.BorderLineThickness * 2), resizeHeight + borderHeight + (float)(borderInterface.BorderLineThickness * 2));
476                 Interop.ActorInternal.SetPosition(GetBorderWindowRootLayer().SwigCPtr, 0, height + borderInterface.BorderLineThickness);
477                 if (contentsView != null)
478                 {
479                     contentsView.SizeHeight = resizeHeight;
480                 }
481                 DoOverlayMode(false);
482             }
483
484             if (NDalicPINVOKE.SWIGPendingException.Pending) { throw NDalicPINVOKE.SWIGPendingException.Retrieve(); }
485         }
486
487         internal Layer GetBorderWindowBottomLayer()
488         {
489             if (borderWindowBottomLayer == null)
490             {
491                 borderWindowBottomLayer = new Layer();
492                 borderWindowBottomLayer.Name = "BorderWindowBottomLayer";
493                 using Vector3 topCentor = new Vector3(0.5f, 0.0f, 0.5f);
494                 Interop.ActorInternal.SetParentOrigin(borderWindowBottomLayer.SwigCPtr, topCentor.SwigCPtr);
495                 Interop.Actor.SetAnchorPoint(borderWindowBottomLayer.SwigCPtr, topCentor.SwigCPtr);
496                 Interop.Actor.Add(rootLayer.SwigCPtr, borderWindowBottomLayer.SwigCPtr);
497                 Interop.ActorInternal.SetSize(borderWindowBottomLayer.SwigCPtr, WindowSize.Width + (float)(borderInterface.BorderLineThickness * 2), WindowSize.Height + (float)(borderInterface.BorderLineThickness * 2));
498                 borderWindowBottomLayer.SetWindow(this);
499                 borderWindowBottomLayer.LowerToBottom();
500
501                 if (NDalicPINVOKE.SWIGPendingException.Pending) { throw NDalicPINVOKE.SWIGPendingException.Retrieve(); }
502             }
503             return borderWindowBottomLayer;
504         }
505
506         internal Layer GetBorderWindowRootLayer()
507         {
508             if (borderWindowRootLayer == null)
509             {
510                 borderWindowRootLayer = new Layer();
511                 borderWindowRootLayer.Name = "RootLayer";
512                 using Vector3 topCentor = new Vector3(0.5f, 0.0f, 0.5f);
513                 Interop.ActorInternal.SetParentOrigin(borderWindowRootLayer.SwigCPtr, topCentor.SwigCPtr);
514                 Interop.Actor.SetAnchorPoint(borderWindowRootLayer.SwigCPtr, topCentor.SwigCPtr);
515                 Interop.Actor.Add(rootLayer.SwigCPtr, borderWindowRootLayer.SwigCPtr);
516                 Interop.ActorInternal.SetSize(borderWindowRootLayer.SwigCPtr, WindowSize.Width, WindowSize.Height - borderHeight - borderInterface.BorderLineThickness * 2);
517                 float height = (isTop == true) ? topView.SizeHeight : 0;
518                 Interop.ActorInternal.SetPosition(borderWindowRootLayer.SwigCPtr, 0, height + borderInterface.BorderLineThickness);
519                 using PropertyValue propertyValue = new Tizen.NUI.PropertyValue((int)Tizen.NUI.ClippingModeType.ClipToBoundingBox);
520                 Tizen.NUI.Object.SetProperty(borderWindowRootLayer.SwigCPtr, Tizen.NUI.BaseComponents.View.Property.ClippingMode, propertyValue);
521
522                 if (NDalicPINVOKE.SWIGPendingException.Pending) { throw NDalicPINVOKE.SWIGPendingException.Retrieve(); }
523             }
524
525             return borderWindowRootLayer;
526         }
527
528         internal void DisposeBorder()
529         {
530             Resized -= OnBorderWindowResized;
531             FocusChanged -= OnWindowFocusChanged;
532             borderInterface.Dispose();
533             GetBorderWindowBottomLayer().Dispose();
534         }
535
536         private void convertBorderWindowSizeToRealWindowSize(Uint16Pair size)
537         {
538             if (isBorderWindow == true)
539             {
540                 var height = (ushort)(size.GetHeight() + borderHeight + borderInterface.BorderLineThickness * 2);
541                 var width = (ushort)(size.GetWidth() + borderInterface.BorderLineThickness * 2);
542                 size.SetHeight(height);
543                 size.SetWidth(width);
544             }
545         }
546
547         private void convertRealWindowSizeToBorderWindowSize(Uint16Pair size)
548         {
549             if (isBorderWindow == true && !(borderInterface.OverlayMode == true && IsMaximized() == true))
550             {
551                 var height = (ushort)(size.GetHeight() - borderHeight - borderInterface.BorderLineThickness * 2);
552                 var width = (ushort)(size.GetWidth() - borderInterface.BorderLineThickness * 2);
553                 size.SetHeight(height);
554                 size.SetWidth(width);
555             }
556         }
557         #endregion //Methods
558
559         #region Structs
560         #endregion //Structs
561
562         #region Classes
563         // View class for border view.
564         private class BorderView : View
565         {
566             private bool overlayEnabled = false;
567
568             /// <summary>
569             /// Set whether or not it is in overlay mode.
570             /// The borderView's OverlayMode means that it is displayed at the top of the screen.
571             /// In this case, the borderView should pass the event so that lower layers can receive the event.
572             /// </summary>
573             /// <param name="enabled">The true if borderView is Overlay mode. false otherwise.</param>
574             [EditorBrowsable(EditorBrowsableState.Never)]
575             public void OverlayMode(bool enabled)
576             {
577                 overlayEnabled = enabled;
578             }
579
580             protected override bool HitTest(Touch touch)
581             {
582                 // If borderView is in overlay mode, pass the hittest.
583                 if (overlayEnabled == true)
584                 {
585                     return false;
586                 }
587                 return true;
588             }
589         }
590         #endregion //Classes
591     }
592 }