[NUI] Fix not to propagate gesture from scrim
[platform/core/csapi/tizenfx.git] / src / Tizen.NUI.Components / Controls / Menu.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.Collections.Generic;
19 using System.ComponentModel;
20 using Tizen.NUI.BaseComponents;
21
22 namespace Tizen.NUI.Components
23 {
24     /// <summary>
25     /// Menu is a class which contains a set of MenuItems and has one of them selected.
26     /// </summary>
27     /// <since_tizen> 9 </since_tizen>
28     public partial class Menu : Control
29     {
30         private Window window = null;
31         private Layer layer = null;
32         private View content = null;
33         private View scrim = null;
34         private View anchor = null;
35         private IEnumerable<MenuItem> menuItems = null;
36         private MenuItemGroup menuItemGroup = null;
37         private RelativePosition horizontalPosition = RelativePosition.Center;
38         private RelativePosition verticalPosition = RelativePosition.Center;
39         private MenuStyle menuStyle = null;
40         private bool styleApplied = false;
41
42         /// <summary>
43         /// Creates a new instance of Menu.
44         /// </summary>
45         /// <since_tizen> 9 </since_tizen>
46         public Menu() : base()
47         {
48             Initialize();
49         }
50
51         /// <summary>
52         /// Creates a new instance of Menu.
53         /// </summary>
54         /// <param name="style">Creates Menu by special style defined in UX.</param>
55         [EditorBrowsable(EditorBrowsableState.Never)]
56         public Menu(string style) : base(style)
57         {
58             Initialize();
59         }
60
61         /// <summary>
62         /// Creates a new instance of a Menu with style.
63         /// </summary>
64         /// <param name="style">A style applied to the newly created Menu.</param>
65         [EditorBrowsable(EditorBrowsableState.Never)]
66         public Menu(MenuStyle style) : base(style)
67         {
68             Initialize();
69         }
70
71         /// <inheritdoc/>
72         [EditorBrowsable(EditorBrowsableState.Never)]
73         protected override void Dispose(DisposeTypes type)
74         {
75             if (disposed)
76             {
77                 return;
78             }
79
80             if (type == DisposeTypes.Explicit)
81             {
82                 if (Content != null)
83                 {
84                     if (menuItems != null)
85                     {
86                         foreach (MenuItem menuItem in menuItems)
87                         {
88                             Content.Remove(menuItem);
89                         }
90                     }
91
92                     Utility.Dispose(Content);
93                 }
94
95                 Utility.Dispose(Scrim);
96
97                 menuItemGroup = null;
98
99                 layer.Remove(this);
100                 Window.RemoveLayer(layer);
101                 layer.Dispose();
102             }
103
104             base.Dispose(type);
105         }
106
107         /// <summary>
108         /// Applies style to MenuItem.
109         /// </summary>
110         /// <param name="viewStyle">The style to apply.</param>
111         [EditorBrowsable(EditorBrowsableState.Never)]
112         public override void ApplyStyle(ViewStyle viewStyle)
113         {
114             styleApplied = false;
115
116             base.ApplyStyle(viewStyle);
117
118             menuStyle = viewStyle as MenuStyle;
119             if (menuStyle != null)
120             {
121                 Content?.ApplyStyle(menuStyle.Content);
122             }
123
124             styleApplied = true;
125         }
126
127         /// <summary>The Menu's relative position to Anchor.</summary>
128         /// <since_tizen> 9 </since_tizen>
129         public enum RelativePosition
130         {
131             /// <summary>
132             /// At the start of the Anchor.
133             /// If this is used with <see cref="HorizontalPositionToAnchor"/>, then Menu is positioned to the left (LTR) of the Anchor or right (RTL) of the Anchor.
134             /// If this is used with <see cref="VerticalPositionToAnchor"/>, then Menu is positioned to the top of the Anchor.
135             ///</summary>
136             /// <since_tizen> 9 </since_tizen>
137             Start = 0,
138             /// <summary>
139             /// At the center of the Anchor.
140             /// If this is used with <see cref="HorizontalPositionToAnchor"/> or <see cref="VerticalPositionToAnchor"/>, then Menu is positioned to the middle of the Anchor.
141             /// </summary>
142             /// <since_tizen> 9 </since_tizen>
143             Center = 1,
144             /// <summary>
145             /// At the end of the Anchor.
146             /// If this is used with <see cref="HorizontalPositionToAnchor"/>, then Menu is positioned to the right (LTR) of the Anchor or left (RTL) of the Anchor.
147             /// If this is used with <see cref="VerticalPositionToAnchor"/>, then Menu is positioned to the bottom of the Anchor.
148             /// </summary>
149             /// <since_tizen> 9 </since_tizen>
150             End = 2,
151         }
152
153         /// <summary>
154         /// Menu items in Menu.
155         /// Menu items are not automatically disposed when Menu is disposed.
156         /// Therefore, please dispose Menu items when you dispose Menu.
157         /// </summary>
158         /// <since_tizen> 9 </since_tizen>
159         public IEnumerable<MenuItem> Items
160         {
161             get
162             {
163                 return menuItems;
164             }
165
166             set
167             {
168                 if (Content == null)
169                 {
170                     Content = CreateDefaultContent();
171                     if (styleApplied && (menuStyle != null))
172                     {
173                         Content.ApplyStyle(menuStyle.Content);
174                     }
175                 }
176
177                 if (menuItems != null)
178                 {
179                     foreach (var oldItem in menuItems)
180                     {
181                         if (Content.Children?.Contains(oldItem) == true)
182                         {
183                             Content.Remove(oldItem);
184                         }
185                     }
186                 }
187
188                 menuItems = value;
189
190                 if (menuItems == null)
191                 {
192                     Content.SetVisible(false);
193                     return;
194                 }
195
196                 if (Content.Visibility == false)
197                 {
198                     Content.SetVisible(true);
199                 }
200
201                 foreach (var item in menuItems)
202                 {
203                     Content.Add(item);
204                     menuItemGroup.Add(item);
205                 }
206             }
207         }
208
209         /// <summary>
210         /// Anchor of Menu.
211         /// Menu is displayed at the anchor's position.
212         /// If there is not enough space to display menu at the anchor's position,
213         /// then menu is displayed at the proper position near anchor's position.
214         /// </summary>
215         /// <since_tizen> 9 </since_tizen>
216         public View Anchor
217         {
218             get
219             {
220                 return GetValue(AnchorProperty) as View;
221             }
222             set
223             {
224                 SetValue(AnchorProperty, value);
225                 NotifyPropertyChanged();
226             }
227         }
228         private View InternalAnchor
229         {
230             get
231             {
232                 return anchor;
233             }
234
235             set
236             {
237                 if (anchor == value)
238                 {
239                     return;
240                 }
241
242                 anchor = value;
243                 if (anchor == null)
244                 {
245                     return;
246                 }
247
248                 CalculateSizeAndPosition();
249             }
250         }
251
252         /// <summary>
253         /// The horizontal position of Menu relative to Anchor.
254         /// If Anchor is not set, then RelativePosition does not work.
255         /// If RelativePosition is Start, then Menu is displayed at the start of Anchor.
256         /// If RelativePosition is Center, then Menu is displayed at the center of Anchor.
257         /// If RelativePosition is End, then Menu is displayed at the end of Anchor.
258         /// If there is not enough space to display menu at the anchor's position,
259         /// then menu is displayed at the proper position near anchor's position.
260         /// </summary>
261         /// <since_tizen> 9 </since_tizen>
262         public RelativePosition HorizontalPositionToAnchor
263         {
264             get
265             {
266                 return (RelativePosition)GetValue(HorizontalPositionToAnchorProperty);
267             }
268             set
269             {
270                 SetValue(HorizontalPositionToAnchorProperty, value);
271                 NotifyPropertyChanged();
272             }
273         }
274         private RelativePosition InternalHorizontalPositionToAnchor
275         {
276             get
277             {
278                 return horizontalPosition;
279             }
280
281             set
282             {
283                 if (horizontalPosition == value)
284                 {
285                     return;
286                 }
287
288                 horizontalPosition = value;
289
290                 CalculateSizeAndPosition();
291             }
292         }
293
294         /// <summary>
295         /// The vertical position of Menu relative to Anchor.
296         /// If Anchor is not set, then RelativePosition does not work.
297         /// If RelativePosition is Start, then Menu is displayed at the start of Anchor.
298         /// If RelativePosition is Center, then Menu is displayed at the center of Anchor.
299         /// If RelativePosition is End, then Menu is displayed at the end of Anchor.
300         /// If there is not enough space to display menu at the anchor's position,
301         /// then menu is displayed at the proper position near anchor's position.
302         /// </summary>
303         /// <since_tizen> 9 </since_tizen>
304         public RelativePosition VerticalPositionToAnchor
305         {
306             get
307             {
308                 return (RelativePosition)GetValue(VerticalPositionToAnchorProperty);
309             }
310             set
311             {
312                 SetValue(VerticalPositionToAnchorProperty, value);
313                 NotifyPropertyChanged();
314             }
315         }
316         private RelativePosition InternalVerticalPositionToAnchor
317         {
318             get
319             {
320                 return verticalPosition;
321             }
322
323             set
324             {
325                 if (verticalPosition == value)
326                 {
327                     return;
328                 }
329
330                 verticalPosition = value;
331
332                 CalculateSizeAndPosition();
333             }
334         }
335
336         /// <summary>
337         /// Gets Menu style.
338         /// </summary>
339         /// <returns>The default Menu style.</returns>
340         [EditorBrowsable(EditorBrowsableState.Never)]
341         protected override ViewStyle CreateViewStyle()
342         {
343             return new MenuStyle();
344         }
345
346         /// <summary>
347         /// Content of Menu.
348         /// </summary>
349         [EditorBrowsable(EditorBrowsableState.Never)]
350         protected View Content
351         {
352             get
353             {
354                 return content;
355             }
356             set
357             {
358                 if (content == value)
359                 {
360                     return;
361                 }
362
363                 if (content != null)
364                 {
365                     Remove(content);
366                 }
367
368                 content = value;
369                 if (content == null)
370                 {
371                     return;
372                 }
373
374                 Add(content);
375
376                 if (Scrim != null)
377                 {
378                     content.RaiseAbove(Scrim);
379                 }
380             }
381         }
382
383         /// <summary>
384         /// Scrim of Menu.
385         /// Scrim is the screen region outside Menu.
386         /// If Scrim is touched, then Menu is dismissed.
387         /// </summary>
388         [EditorBrowsable(EditorBrowsableState.Never)]
389         protected View Scrim
390         {
391             get
392             {
393                 return scrim;
394             }
395             set
396             {
397                 if (scrim == value)
398                 {
399                     return;
400                 }
401
402                 if (scrim != null)
403                 {
404                     Remove(scrim);
405                 }
406
407                 scrim = value;
408                 if (scrim == null)
409                 {
410                     return;
411                 }
412
413                 Add(scrim);
414
415                 if (Content != null)
416                 {
417                     Content.RaiseAbove(scrim);
418                 }
419             }
420         }
421
422         private Window Window
423         {
424             get
425             {
426                 if (window == null)
427                 {
428                     window = NUIApplication.GetDefaultWindow();
429                 }
430
431                 return window;
432             }
433             set
434             {
435                 if (window == value)
436                 {
437                     return;
438                 }
439
440                 window = value;
441             }
442         }
443
444         /// <summary>
445         /// Post the Menu.
446         /// The Menu is displayed.
447         /// </summary>
448         /// <param name="window">The Window where Menu is displayed.</param>
449         /// <since_tizen> 9 </since_tizen>
450         public void Post(Window window = null)
451         {
452             if (window == null)
453             {
454                 window = NUIApplication.GetDefaultWindow();
455             }
456
457             Window = window;
458
459             Window.AddLayer(layer);
460             layer.RaiseToTop();
461
462             CalculateSizeAndPosition();
463             RegisterDefaultLabel();
464             NotifyAccessibilityStatesChange(new AccessibilityStates(AccessibilityState.Visible, AccessibilityState.Showing), AccessibilityStatesNotifyMode.Recursive);
465         }
466
467         /// <summary>
468         /// Dismiss the Menu.
469         /// The Menu becomes hidden and disposed.
470         /// </summary>
471         /// <since_tizen> 9 </since_tizen>
472         public void Dismiss()
473         {
474             Hide();
475             UnregisterDefaultLabel();
476             NotifyAccessibilityStatesChange(new AccessibilityStates(AccessibilityState.Visible, AccessibilityState.Showing), AccessibilityStatesNotifyMode.Recursive);
477             Dispose();
478         }
479
480         /// <inheritdoc/>
481         [EditorBrowsable(EditorBrowsableState.Never)]
482         public override void OnRelayout(Vector2 size, RelayoutContainer container)
483         {
484             base.OnRelayout(size, container);
485
486             CalculateSizeAndPosition();
487         }
488
489         private void Initialize()
490         {
491             Layout = new AbsoluteLayout();
492
493             WidthSpecification = LayoutParamPolicies.WrapContent;
494             HeightSpecification = LayoutParamPolicies.WrapContent;
495
496             // Menu is added to Anchor so Menu should exclude layouting because
497             // if Anchor has Layout, then Menu is displayed at an incorrect position.
498             ExcludeLayouting = true;
499
500             Scrim = CreateDefaultScrim();
501
502             menuItemGroup = new MenuItemGroup();
503
504             layer = new Layer();
505             layer.Add(this);
506         }
507
508         private ScrollableBase CreateDefaultContent()
509         {
510             return new ScrollableBase()
511             {
512                 Layout = new LinearLayout()
513                 {
514                     LinearOrientation = LinearLayout.Orientation.Vertical,
515                 },
516                 ScrollingDirection = ScrollableBase.Direction.Vertical,
517                 ScrollEnabled = true,
518                 HideScrollbar = false,
519                 ClippingMode = ClippingModeType.ClipChildren,
520             };
521         }
522
523         private View CreateDefaultScrim()
524         {
525             var scrim = new VisualView()
526             {
527                 // Scrim is added to Menu so Scrim should exclude layouting
528                 // not to enlarge Menu size.
529                 ExcludeLayouting = true,
530                 BackgroundColor = Color.Transparent,
531                 Size = new Size(NUIApplication.GetDefaultWindow().Size),
532                 DispatchParentGestureEvents = false,
533             };
534
535             scrim.TouchEvent += (object source, TouchEventArgs e) =>
536             {
537                 if (e.Touch.GetState(0) == PointStateType.Up)
538                 {
539                     this.Dismiss();
540                 }
541                 return true;
542             };
543
544             return scrim;
545         }
546
547         private void CalculateSizeAndPosition()
548         {
549             CalculateMenuPosition();
550
551             CalculateScrimPosition();
552         }
553
554         private View GetRootView()
555         {
556             View root = this;
557             View parent = GetParent() as View;
558
559             while (parent)
560             {
561                 root = parent;
562                 parent = parent?.GetParent() as View;
563             }
564
565             return root;
566         }
567
568         // Calculate menu's position based on Anchor and parent's positions.
569         // If there is not enought space, then menu's size can be also resized.
570         private void CalculateMenuPosition()
571         {
572             if (Anchor == null)
573             {
574                 return;
575             }
576
577             if (SizeWidth.Equals(0) && SizeHeight.Equals(0))
578             {
579                 return;
580             }
581
582             float menuScreenPosX = 0;
583             float menuScreenPosY = 0;
584
585             if (HorizontalPositionToAnchor == RelativePosition.Start)
586             {
587                 if (GetRootView().LayoutDirection == ViewLayoutDirectionType.LTR)
588                 {
589                     menuScreenPosX = Anchor.ScreenPosition.X - SizeWidth;
590                 }
591                 else
592                 {
593                     menuScreenPosX = Anchor.ScreenPosition.X + Anchor.Margin.Start + Anchor.SizeWidth + Anchor.Margin.End;
594                 }
595             }
596             else if (HorizontalPositionToAnchor == RelativePosition.Center)
597             {
598                 menuScreenPosX = Anchor.ScreenPosition.X + Anchor.Margin.Start + (Anchor.SizeWidth / 2) - (SizeWidth / 2);
599             }
600             else
601             {
602                 if (GetRootView().LayoutDirection == ViewLayoutDirectionType.LTR)
603                 {
604                     menuScreenPosX = Anchor.ScreenPosition.X + Anchor.Margin.Start + Anchor.SizeWidth + Anchor.Margin.End;
605                 }
606                 else
607                 {
608                     menuScreenPosX = Anchor.ScreenPosition.X - SizeWidth;
609                 }
610             }
611
612             if (VerticalPositionToAnchor == RelativePosition.Start)
613             {
614                 menuScreenPosY = Anchor.ScreenPosition.Y - SizeHeight;
615             }
616             else if (VerticalPositionToAnchor == RelativePosition.Center)
617             {
618                 menuScreenPosY = Anchor.ScreenPosition.Y + Anchor.Margin.Top + (Anchor.SizeHeight / 2) - (SizeHeight / 2);
619             }
620             else
621             {
622                 menuScreenPosY = Anchor.ScreenPosition.Y + Anchor.Margin.Top + Anchor.SizeHeight + Anchor.Margin.Bottom;
623             }
624
625             float menuSizeW = SizeWidth;
626             float menuSizeH = SizeHeight;
627
628             // Check if menu is not inside parent's boundary in x coordinate system.
629             if (menuScreenPosX + SizeWidth > Window.Size.Width)
630             {
631                 if (HorizontalPositionToAnchor == RelativePosition.Center)
632                 {
633                     menuScreenPosX = Window.Size.Width - SizeWidth;
634                 }
635                 else
636                 {
637                     menuSizeW = Window.Size.Width - menuScreenPosX;
638                 }
639             }
640             if (menuScreenPosX < 0)
641             {
642                 menuScreenPosX = 0;
643
644                 if (menuSizeW > Window.Size.Width)
645                 {
646                     menuSizeW = Window.Size.Width;
647                 }
648             }
649
650             // Check if menu is not inside parent's boundary in y coordinate system.
651             if (menuScreenPosY + SizeHeight > Window.Size.Height)
652             {
653                 if (VerticalPositionToAnchor == RelativePosition.Center)
654                 {
655                     menuScreenPosY = Window.Size.Height - SizeHeight;
656                 }
657                 else
658                 {
659                     menuSizeH = Window.Size.Height - menuScreenPosY;
660                 }
661             }
662             if (menuScreenPosY < 0)
663             {
664                 menuScreenPosY = 0;
665
666                 if (menuSizeH > Window.Size.Height)
667                 {
668                     menuSizeH = Window.Size.Height;
669                 }
670             }
671
672             // Position is relative to parent's coordinate system.
673             var menuPosX = menuScreenPosX;
674             var menuPosY = menuScreenPosY;
675
676             if (!PositionX.Equals(menuPosX) || !PositionY.Equals(menuPosY) || !SizeWidth.Equals(menuSizeW) || !SizeHeight.Equals(menuSizeH))
677             {
678                 Position = new Position(menuPosX, menuPosY);
679                 Size = new Size(menuSizeW, menuSizeH);
680             }
681         }
682
683         // Calculate scrim's position based on menu's position
684         private void CalculateScrimPosition()
685         {
686             if (Scrim == null)
687             {
688                 return;
689             }
690
691             // Menu's Position should be updated before doing this calculation.
692             if (!Scrim.PositionX.Equals(-PositionX) || !Scrim.PositionY.Equals(-PositionY))
693             {
694                 Scrim.Position = new Position(-PositionX, -PositionY);
695             }
696         }
697
698         /// <summary>
699         /// Initialize AT-SPI object.
700         /// </summary>
701         [EditorBrowsable(EditorBrowsableState.Never)]
702         public override void OnInitialize()
703         {
704             base.OnInitialize();
705             AccessibilityRole = Role.PopupMenu;
706         }
707
708         /// <summary>
709         /// Informs AT-SPI bridge about the set of AT-SPI states associated with this object.
710         /// </summary>
711         [EditorBrowsable(EditorBrowsableState.Never)]
712         protected override AccessibilityStates AccessibilityCalculateStates()
713         {
714             var states = base.AccessibilityCalculateStates();
715
716             states[AccessibilityState.Modal] = true;
717
718             return states;
719         }
720     }
721 }