2 * Copyright(c) 2021 Samsung Electronics Co., Ltd.
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
18 using System.Collections.Generic;
19 using System.ComponentModel;
20 using Tizen.NUI.BaseComponents;
22 namespace Tizen.NUI.Components
25 /// Menu is a class which contains a set of MenuItems and has one of them selected.
27 /// <since_tizen> 9 </since_tizen>
28 public partial class Menu : Control
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;
41 /// Creates a new instance of Menu.
43 /// <since_tizen> 9 </since_tizen>
44 public Menu() : base()
50 [EditorBrowsable(EditorBrowsableState.Never)]
51 protected override void Dispose(DisposeTypes type)
58 if (type == DisposeTypes.Explicit)
62 if (menuItems != null)
64 foreach (MenuItem menuItem in menuItems)
66 Content.Remove(menuItem);
70 Utility.Dispose(Content);
73 Utility.Dispose(Scrim);
78 Window.RemoveLayer(layer);
85 /// <summary>The Menu's relative position to Anchor.</summary>
86 /// <since_tizen> 9 </since_tizen>
87 public enum RelativePosition
90 /// At the start of the Anchor.
91 /// 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.
92 /// If this is used with <see cref="VerticalPositionToAnchor"/>, then Menu is positioned to the top of the Anchor.
94 /// <since_tizen> 9 </since_tizen>
97 /// At the center of the Anchor.
98 /// If this is used with <see cref="HorizontalPositionToAnchor"/> or <see cref="VerticalPositionToAnchor"/>, then Menu is positioned to the middle of the Anchor.
100 /// <since_tizen> 9 </since_tizen>
103 /// At the end of the Anchor.
104 /// 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.
105 /// If this is used with <see cref="VerticalPositionToAnchor"/>, then Menu is positioned to the bottom of the Anchor.
107 /// <since_tizen> 9 </since_tizen>
112 /// Menu items in Menu.
113 /// Menu items are not automatically disposed when Menu is disposed.
114 /// Therefore, please dispose Menu items when you dispose Menu.
116 /// <since_tizen> 9 </since_tizen>
117 public IEnumerable<MenuItem> Items
126 if (menuItems != null)
128 foreach (var oldItem in menuItems)
130 if (content.Children?.Contains(oldItem) == true)
132 content.Remove(oldItem);
139 if (menuItems == null)
144 foreach (var item in menuItems)
147 menuItemGroup.Add(item);
154 /// Menu is displayed at the anchor's position.
155 /// If there is not enough space to display menu at the anchor's position,
156 /// then menu is displayed at the proper position near anchor's position.
158 /// <since_tizen> 9 </since_tizen>
163 return GetValue(AnchorProperty) as View;
167 SetValue(AnchorProperty, value);
168 NotifyPropertyChanged();
171 private View InternalAnchor
191 CalculateSizeAndPosition();
196 /// The horizontal position of Menu relative to Anchor.
197 /// If Anchor is not set, then RelativePosition does not work.
198 /// If RelativePosition is Start, then Menu is displayed at the start of Anchor.
199 /// If RelativePosition is Center, then Menu is displayed at the center of Anchor.
200 /// If RelativePosition is End, then Menu is displayed at the end of Anchor.
201 /// If there is not enough space to display menu at the anchor's position,
202 /// then menu is displayed at the proper position near anchor's position.
204 /// <since_tizen> 9 </since_tizen>
205 public RelativePosition HorizontalPositionToAnchor
209 return (RelativePosition)GetValue(HorizontalPositionToAnchorProperty);
213 SetValue(HorizontalPositionToAnchorProperty, value);
214 NotifyPropertyChanged();
217 private RelativePosition InternalHorizontalPositionToAnchor
221 return horizontalPosition;
226 if (horizontalPosition == value)
231 horizontalPosition = value;
233 CalculateSizeAndPosition();
238 /// The vertical position of Menu relative to Anchor.
239 /// If Anchor is not set, then RelativePosition does not work.
240 /// If RelativePosition is Start, then Menu is displayed at the start of Anchor.
241 /// If RelativePosition is Center, then Menu is displayed at the center of Anchor.
242 /// If RelativePosition is End, then Menu is displayed at the end of Anchor.
243 /// If there is not enough space to display menu at the anchor's position,
244 /// then menu is displayed at the proper position near anchor's position.
246 /// <since_tizen> 9 </since_tizen>
247 public RelativePosition VerticalPositionToAnchor
251 return (RelativePosition)GetValue(VerticalPositionToAnchorProperty);
255 SetValue(VerticalPositionToAnchorProperty, value);
256 NotifyPropertyChanged();
259 private RelativePosition InternalVerticalPositionToAnchor
263 return verticalPosition;
268 if (verticalPosition == value)
273 verticalPosition = value;
275 CalculateSizeAndPosition();
283 [EditorBrowsable(EditorBrowsableState.Never)]
284 protected View Content
292 if (content == value)
312 content.RaiseAbove(Scrim);
319 /// Scrim is the screen region outside Menu.
320 /// If Scrim is touched, then Menu is dismissed.
322 [EditorBrowsable(EditorBrowsableState.Never)]
351 Content.RaiseAbove(scrim);
356 private Window Window
362 window = NUIApplication.GetDefaultWindow();
380 /// The Menu is displayed.
382 /// <param name="window">The Window where Menu is displayed.</param>
383 /// <since_tizen> 9 </since_tizen>
384 public void Post(Window window = null)
388 window = NUIApplication.GetDefaultWindow();
393 Window.AddLayer(layer);
396 CalculateSizeAndPosition();
397 RegisterDefaultLabel();
398 NotifyAccessibilityStatesChange(AccessibilityStates.Visible | AccessibilityStates.Showing, AccessibilityStatesNotifyMode.Recursive);
402 /// Dismiss the Menu.
403 /// The Menu becomes hidden and disposed.
405 /// <since_tizen> 9 </since_tizen>
406 public void Dismiss()
409 UnregisterDefaultLabel();
410 NotifyAccessibilityStatesChange(AccessibilityStates.Visible | AccessibilityStates.Showing, AccessibilityStatesNotifyMode.Recursive);
415 [EditorBrowsable(EditorBrowsableState.Never)]
416 public override void OnRelayout(Vector2 size, RelayoutContainer container)
418 base.OnRelayout(size, container);
420 CalculateSizeAndPosition();
423 private void Initialize()
425 Layout = new AbsoluteLayout();
427 WidthSpecification = LayoutParamPolicies.WrapContent;
428 HeightSpecification = LayoutParamPolicies.WrapContent;
430 BackgroundColor = Color.Transparent;
432 // Menu is added to Anchor so Menu should exclude layouting because
433 // if Anchor has Layout, then Menu is displayed at an incorrect position.
434 ExcludeLayouting = true;
436 Content = CreateDefaultContent();
438 Scrim = CreateDefaultScrim();
440 menuItemGroup = new MenuItemGroup();
446 private ScrollableBase CreateDefaultContent()
448 return new ScrollableBase()
450 Layout = new LinearLayout()
452 LinearOrientation = LinearLayout.Orientation.Vertical,
454 ScrollingDirection = ScrollableBase.Direction.Vertical,
455 ScrollEnabled = true,
456 HideScrollbar = false,
458 // FIXME: This color should be in DefaultThemeCommon.cs.
459 BackgroundColor = new Color("#EEEFF1"),
463 private View CreateDefaultScrim()
465 var scrim = new VisualView()
467 // Scrim is added to Menu so Scrim should exclude layouting
468 // not to enlarge Menu size.
469 ExcludeLayouting = true,
470 BackgroundColor = Color.Transparent,
471 Size = new Size(NUIApplication.GetDefaultWindow().Size),
474 scrim.TouchEvent += (object source, TouchEventArgs e) =>
476 if (e.Touch.GetState(0) == PointStateType.Up)
486 private void CalculateSizeAndPosition()
490 CalculateMenuPosition();
492 CalculateScrimPosition();
495 // Calculate menu's size based on content's size
496 private void CalculateMenuSize()
503 if (Size2D.Equals(Content.Size2D) == false)
505 Size2D = new Size2D(Content.Size2D.Width, Content.Size2D.Height);
509 private View GetRootView()
512 View parent = GetParent() as View;
517 parent = parent?.GetParent() as View;
523 // Calculate menu's position based on Anchor and parent's positions.
524 // If there is not enought space, then menu's size can be also resized.
525 private void CalculateMenuPosition()
527 if ((Anchor == null) || (Content == null))
537 if ((Size2D.Width == 0) && (Size2D.Height == 0))
542 int menuScreenPosX = 0;
543 int menuScreenPosY = 0;
545 if (HorizontalPositionToAnchor == RelativePosition.Start)
547 if (GetRootView().LayoutDirection == ViewLayoutDirectionType.LTR)
549 menuScreenPosX = (int)Anchor.ScreenPosition.X - Size2D.Width;
553 menuScreenPosX = (int)Anchor.ScreenPosition.X + Anchor.Margin.Start + Anchor.Size2D.Width + Anchor.Margin.End;
556 else if (HorizontalPositionToAnchor == RelativePosition.Center)
558 menuScreenPosX = (int)Anchor.ScreenPosition.X + Anchor.Margin.Start + (Anchor.Size2D.Width / 2) - (Size2D.Width / 2);
562 if (GetRootView().LayoutDirection == ViewLayoutDirectionType.LTR)
564 menuScreenPosX = (int)Anchor.ScreenPosition.X + Anchor.Margin.Start + Anchor.Size2D.Width + Anchor.Margin.End;
568 menuScreenPosX = (int)Anchor.ScreenPosition.X - Size2D.Width;
572 if (VerticalPositionToAnchor == RelativePosition.Start)
574 menuScreenPosY = (int)Anchor.ScreenPosition.Y - Size2D.Height;
576 else if (VerticalPositionToAnchor == RelativePosition.Center)
578 menuScreenPosY = (int)Anchor.ScreenPosition.Y + Anchor.Margin.Top + (Anchor.Size2D.Height / 2) - (Size2D.Height / 2);
582 menuScreenPosY = (int)Anchor.ScreenPosition.Y + Anchor.Margin.Top + Anchor.Size2D.Height + Anchor.Margin.Bottom;
585 int menuSizeW = Size2D.Width;
586 int menuSizeH = Size2D.Height;
588 // Check if menu is not inside parent's boundary in x coordinate system.
589 if (menuScreenPosX + Size2D.Width > Window.Size.Width)
591 menuScreenPosX = Window.Size.Width - Size2D.Width;
593 if (menuScreenPosX < 0)
596 menuSizeW = Window.Size.Width;
599 else if (menuScreenPosX < 0)
602 menuSizeW = Window.Size.Width;
605 // Check if menu is not inside parent's boundary in y coordinate system.
606 if (menuScreenPosY + Size2D.Height > Window.Size.Height)
608 menuScreenPosY = Window.Size.Height - Size2D.Height;
610 if (menuScreenPosY < 0)
613 menuSizeH = Window.Size.Height;
616 else if (menuScreenPosY < 0)
619 menuSizeH = Window.Size.Height;
622 // Position is relative to parent's coordinate system.
623 var menuPosX = menuScreenPosX;
624 var menuPosY = menuScreenPosY;
626 if ((Position2D.X != menuPosX) || (Position2D.Y != menuPosY) || (Size2D.Width != menuSizeW) || (Size2D.Height != menuSizeH))
628 Position2D = new Position2D(menuPosX, menuPosY);
629 Size2D = new Size2D(menuSizeW, menuSizeH);
633 // Calculate scrim's position based on menu's position
634 private void CalculateScrimPosition()
641 // Menu's Position should be updated before doing this calculation.
642 if ((Scrim.Position2D.X != -Position2D.X) || (Scrim.Position2D.Y != -Position2D.Y))
644 Scrim.Position2D = new Position2D(-Position2D.X, -Position2D.Y);
649 /// Initialize AT-SPI object.
651 [EditorBrowsable(EditorBrowsableState.Never)]
652 public override void OnInitialize()
655 SetAccessibilityConstructor(Role.PopupMenu);
656 AppendAccessibilityAttribute("sub-role", "Alert");
660 /// Informs AT-SPI bridge about the set of AT-SPI states associated with this object.
662 [EditorBrowsable(EditorBrowsableState.Never)]
663 protected override AccessibilityStates AccessibilityCalculateStates(ulong states)
665 var accessibilityStates = base.AccessibilityCalculateStates(states);
666 FlagSetter(ref accessibilityStates, AccessibilityStates.Modal, true);
667 return accessibilityStates;