2 * Copyright(c) 2022 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;
39 private MenuStyle menuStyle = null;
40 private bool styleApplied = false;
43 /// Creates a new instance of Menu.
45 /// <since_tizen> 9 </since_tizen>
46 public Menu() : base()
52 /// Creates a new instance of Menu.
54 /// <param name="style">Creates Menu by special style defined in UX.</param>
55 [EditorBrowsable(EditorBrowsableState.Never)]
56 public Menu(string style) : base(style)
62 /// Creates a new instance of a Menu with style.
64 /// <param name="style">A style applied to the newly created Menu.</param>
65 [EditorBrowsable(EditorBrowsableState.Never)]
66 public Menu(MenuStyle style) : base(style)
72 [EditorBrowsable(EditorBrowsableState.Never)]
73 protected override void Dispose(DisposeTypes type)
80 if (type == DisposeTypes.Explicit)
84 if (menuItems != null)
86 foreach (MenuItem menuItem in menuItems)
88 Content.Remove(menuItem);
92 Utility.Dispose(Content);
95 Utility.Dispose(Scrim);
100 Window.RemoveLayer(layer);
108 /// Applies style to MenuItem.
110 /// <param name="viewStyle">The style to apply.</param>
111 [EditorBrowsable(EditorBrowsableState.Never)]
112 public override void ApplyStyle(ViewStyle viewStyle)
114 styleApplied = false;
116 base.ApplyStyle(viewStyle);
118 menuStyle = viewStyle as MenuStyle;
119 if (menuStyle != null)
121 Content?.ApplyStyle(menuStyle.Content);
127 /// <summary>The Menu's relative position to Anchor.</summary>
128 /// <since_tizen> 9 </since_tizen>
129 public enum RelativePosition
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.
136 /// <since_tizen> 9 </since_tizen>
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.
142 /// <since_tizen> 9 </since_tizen>
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.
149 /// <since_tizen> 9 </since_tizen>
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.
158 /// <since_tizen> 9 </since_tizen>
159 public IEnumerable<MenuItem> Items
170 Content = CreateDefaultContent();
171 if (styleApplied && (menuStyle != null))
173 Content.ApplyStyle(menuStyle.Content);
177 if (menuItems != null)
179 foreach (var oldItem in menuItems)
181 if (Content.Children?.Contains(oldItem) == true)
183 Content.Remove(oldItem);
190 if (menuItems == null)
192 Content.SetVisible(false);
196 if (Content.Visibility == false)
198 Content.SetVisible(true);
201 foreach (var item in menuItems)
204 menuItemGroup.Add(item);
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.
215 /// <since_tizen> 9 </since_tizen>
220 return GetValue(AnchorProperty) as View;
224 SetValue(AnchorProperty, value);
225 NotifyPropertyChanged();
228 private View InternalAnchor
248 CalculateSizeAndPosition();
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.
261 /// <since_tizen> 9 </since_tizen>
262 public RelativePosition HorizontalPositionToAnchor
266 return (RelativePosition)GetValue(HorizontalPositionToAnchorProperty);
270 SetValue(HorizontalPositionToAnchorProperty, value);
271 NotifyPropertyChanged();
274 private RelativePosition InternalHorizontalPositionToAnchor
278 return horizontalPosition;
283 if (horizontalPosition == value)
288 horizontalPosition = value;
290 CalculateSizeAndPosition();
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.
303 /// <since_tizen> 9 </since_tizen>
304 public RelativePosition VerticalPositionToAnchor
308 return (RelativePosition)GetValue(VerticalPositionToAnchorProperty);
312 SetValue(VerticalPositionToAnchorProperty, value);
313 NotifyPropertyChanged();
316 private RelativePosition InternalVerticalPositionToAnchor
320 return verticalPosition;
325 if (verticalPosition == value)
330 verticalPosition = value;
332 CalculateSizeAndPosition();
339 /// <returns>The default Menu style.</returns>
340 [EditorBrowsable(EditorBrowsableState.Never)]
341 protected override ViewStyle CreateViewStyle()
343 return new MenuStyle();
349 [EditorBrowsable(EditorBrowsableState.Never)]
350 protected View Content
358 if (content == value)
378 content.RaiseAbove(Scrim);
385 /// Scrim is the screen region outside Menu.
386 /// If Scrim is touched, then Menu is dismissed.
388 [EditorBrowsable(EditorBrowsableState.Never)]
417 Content.RaiseAbove(scrim);
422 private Window Window
428 window = NUIApplication.GetDefaultWindow();
446 /// The Menu is displayed.
448 /// <param name="window">The Window where Menu is displayed.</param>
449 /// <since_tizen> 9 </since_tizen>
450 public void Post(Window window = null)
454 window = NUIApplication.GetDefaultWindow();
459 Window.AddLayer(layer);
462 CalculateSizeAndPosition();
463 RegisterDefaultLabel();
464 NotifyAccessibilityStatesChange(new AccessibilityStates(AccessibilityState.Visible, AccessibilityState.Showing), AccessibilityStatesNotifyMode.Recursive);
468 /// Dismiss the Menu.
469 /// The Menu becomes hidden and disposed.
471 /// <since_tizen> 9 </since_tizen>
472 public void Dismiss()
475 UnregisterDefaultLabel();
476 NotifyAccessibilityStatesChange(new AccessibilityStates(AccessibilityState.Visible, AccessibilityState.Showing), AccessibilityStatesNotifyMode.Recursive);
481 [EditorBrowsable(EditorBrowsableState.Never)]
482 public override void OnRelayout(Vector2 size, RelayoutContainer container)
484 base.OnRelayout(size, container);
486 CalculateSizeAndPosition();
489 private void Initialize()
491 Layout = new AbsoluteLayout();
493 WidthSpecification = LayoutParamPolicies.WrapContent;
494 HeightSpecification = LayoutParamPolicies.WrapContent;
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;
500 Scrim = CreateDefaultScrim();
502 menuItemGroup = new MenuItemGroup();
508 private ScrollableBase CreateDefaultContent()
510 return new ScrollableBase()
512 Layout = new LinearLayout()
514 LinearOrientation = LinearLayout.Orientation.Vertical,
516 ScrollingDirection = ScrollableBase.Direction.Vertical,
517 ScrollEnabled = true,
518 HideScrollbar = false,
519 ClippingMode = ClippingModeType.ClipChildren,
523 private View CreateDefaultScrim()
525 var scrim = new VisualView()
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,
535 scrim.TouchEvent += (object source, TouchEventArgs e) =>
537 if (e.Touch.GetState(0) == PointStateType.Up)
547 private void CalculateSizeAndPosition()
549 CalculateMenuPosition();
551 CalculateScrimPosition();
554 private View GetRootView()
557 View parent = GetParent() as View;
562 parent = parent?.GetParent() as View;
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()
577 if (SizeWidth.Equals(0) && SizeHeight.Equals(0))
582 float menuScreenPosX = 0;
583 float menuScreenPosY = 0;
585 if (HorizontalPositionToAnchor == RelativePosition.Start)
587 if (GetRootView().LayoutDirection == ViewLayoutDirectionType.LTR)
589 menuScreenPosX = Anchor.ScreenPosition.X - SizeWidth;
593 menuScreenPosX = Anchor.ScreenPosition.X + Anchor.Margin.Start + Anchor.SizeWidth + Anchor.Margin.End;
596 else if (HorizontalPositionToAnchor == RelativePosition.Center)
598 menuScreenPosX = Anchor.ScreenPosition.X + Anchor.Margin.Start + (Anchor.SizeWidth / 2) - (SizeWidth / 2);
602 if (GetRootView().LayoutDirection == ViewLayoutDirectionType.LTR)
604 menuScreenPosX = Anchor.ScreenPosition.X + Anchor.Margin.Start + Anchor.SizeWidth + Anchor.Margin.End;
608 menuScreenPosX = Anchor.ScreenPosition.X - SizeWidth;
612 if (VerticalPositionToAnchor == RelativePosition.Start)
614 menuScreenPosY = Anchor.ScreenPosition.Y - SizeHeight;
616 else if (VerticalPositionToAnchor == RelativePosition.Center)
618 menuScreenPosY = Anchor.ScreenPosition.Y + Anchor.Margin.Top + (Anchor.SizeHeight / 2) - (SizeHeight / 2);
622 menuScreenPosY = Anchor.ScreenPosition.Y + Anchor.Margin.Top + Anchor.SizeHeight + Anchor.Margin.Bottom;
625 float menuSizeW = SizeWidth;
626 float menuSizeH = SizeHeight;
628 // Check if menu is not inside parent's boundary in x coordinate system.
629 if (menuScreenPosX + SizeWidth > Window.Size.Width)
631 if (HorizontalPositionToAnchor == RelativePosition.Center)
633 menuScreenPosX = Window.Size.Width - SizeWidth;
637 menuSizeW = Window.Size.Width - menuScreenPosX;
640 if (menuScreenPosX < 0)
644 if (menuSizeW > Window.Size.Width)
646 menuSizeW = Window.Size.Width;
650 // Check if menu is not inside parent's boundary in y coordinate system.
651 if (menuScreenPosY + SizeHeight > Window.Size.Height)
653 if (VerticalPositionToAnchor == RelativePosition.Center)
655 menuScreenPosY = Window.Size.Height - SizeHeight;
659 menuSizeH = Window.Size.Height - menuScreenPosY;
662 if (menuScreenPosY < 0)
666 if (menuSizeH > Window.Size.Height)
668 menuSizeH = Window.Size.Height;
672 // Position is relative to parent's coordinate system.
673 var menuPosX = menuScreenPosX;
674 var menuPosY = menuScreenPosY;
676 if (!PositionX.Equals(menuPosX) || !PositionY.Equals(menuPosY) || !SizeWidth.Equals(menuSizeW) || !SizeHeight.Equals(menuSizeH))
678 Position = new Position(menuPosX, menuPosY);
679 Size = new Size(menuSizeW, menuSizeH);
683 // Calculate scrim's position based on menu's position
684 private void CalculateScrimPosition()
691 // Menu's Position should be updated before doing this calculation.
692 if (!Scrim.PositionX.Equals(-PositionX) || !Scrim.PositionY.Equals(-PositionY))
694 Scrim.Position = new Position(-PositionX, -PositionY);
699 /// Initialize AT-SPI object.
701 [EditorBrowsable(EditorBrowsableState.Never)]
702 public override void OnInitialize()
705 AccessibilityRole = Role.PopupMenu;
709 /// Informs AT-SPI bridge about the set of AT-SPI states associated with this object.
711 [EditorBrowsable(EditorBrowsableState.Never)]
712 protected override AccessibilityStates AccessibilityCalculateStates()
714 var states = base.AccessibilityCalculateStates();
716 states[AccessibilityState.Modal] = true;