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