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