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),
534 scrim.TouchEvent += (object source, TouchEventArgs e) =>
536 if (e.Touch.GetState(0) == PointStateType.Up)
546 private void CalculateSizeAndPosition()
548 CalculateMenuPosition();
550 CalculateScrimPosition();
553 private View GetRootView()
556 View parent = GetParent() as View;
561 parent = parent?.GetParent() as View;
567 // Calculate menu's position based on Anchor and parent's positions.
568 // If there is not enought space, then menu's size can be also resized.
569 private void CalculateMenuPosition()
576 if (SizeWidth.Equals(0) && SizeHeight.Equals(0))
581 float menuScreenPosX = 0;
582 float menuScreenPosY = 0;
584 if (HorizontalPositionToAnchor == RelativePosition.Start)
586 if (GetRootView().LayoutDirection == ViewLayoutDirectionType.LTR)
588 menuScreenPosX = Anchor.ScreenPosition.X - SizeWidth;
592 menuScreenPosX = Anchor.ScreenPosition.X + Anchor.Margin.Start + Anchor.SizeWidth + Anchor.Margin.End;
595 else if (HorizontalPositionToAnchor == RelativePosition.Center)
597 menuScreenPosX = Anchor.ScreenPosition.X + Anchor.Margin.Start + (Anchor.SizeWidth / 2) - (SizeWidth / 2);
601 if (GetRootView().LayoutDirection == ViewLayoutDirectionType.LTR)
603 menuScreenPosX = Anchor.ScreenPosition.X + Anchor.Margin.Start + Anchor.SizeWidth + Anchor.Margin.End;
607 menuScreenPosX = Anchor.ScreenPosition.X - SizeWidth;
611 if (VerticalPositionToAnchor == RelativePosition.Start)
613 menuScreenPosY = Anchor.ScreenPosition.Y - SizeHeight;
615 else if (VerticalPositionToAnchor == RelativePosition.Center)
617 menuScreenPosY = Anchor.ScreenPosition.Y + Anchor.Margin.Top + (Anchor.SizeHeight / 2) - (SizeHeight / 2);
621 menuScreenPosY = Anchor.ScreenPosition.Y + Anchor.Margin.Top + Anchor.SizeHeight + Anchor.Margin.Bottom;
624 float menuSizeW = SizeWidth;
625 float menuSizeH = SizeHeight;
627 // Check if menu is not inside parent's boundary in x coordinate system.
628 if (menuScreenPosX + SizeWidth > Window.Size.Width)
630 if (HorizontalPositionToAnchor == RelativePosition.Center)
632 menuScreenPosX = Window.Size.Width - SizeWidth;
636 menuSizeW = Window.Size.Width - menuScreenPosX;
639 if (menuScreenPosX < 0)
643 if (menuSizeW > Window.Size.Width)
645 menuSizeW = Window.Size.Width;
649 // Check if menu is not inside parent's boundary in y coordinate system.
650 if (menuScreenPosY + SizeHeight > Window.Size.Height)
652 if (VerticalPositionToAnchor == RelativePosition.Center)
654 menuScreenPosY = Window.Size.Height - SizeHeight;
658 menuSizeH = Window.Size.Height - menuScreenPosY;
661 if (menuScreenPosY < 0)
665 if (menuSizeH > Window.Size.Height)
667 menuSizeH = Window.Size.Height;
671 // Position is relative to parent's coordinate system.
672 var menuPosX = menuScreenPosX;
673 var menuPosY = menuScreenPosY;
675 if (!PositionX.Equals(menuPosX) || !PositionY.Equals(menuPosY) || !SizeWidth.Equals(menuSizeW) || !SizeHeight.Equals(menuSizeH))
677 Position = new Position(menuPosX, menuPosY);
678 Size = new Size(menuSizeW, menuSizeH);
682 // Calculate scrim's position based on menu's position
683 private void CalculateScrimPosition()
690 // Menu's Position should be updated before doing this calculation.
691 if (!Scrim.PositionX.Equals(-PositionX) || !Scrim.PositionY.Equals(-PositionY))
693 Scrim.Position = new Position(-PositionX, -PositionY);
698 /// Initialize AT-SPI object.
700 [EditorBrowsable(EditorBrowsableState.Never)]
701 public override void OnInitialize()
704 AccessibilityRole = Role.PopupMenu;
708 /// Informs AT-SPI bridge about the set of AT-SPI states associated with this object.
710 [EditorBrowsable(EditorBrowsableState.Never)]
711 protected override AccessibilityStates AccessibilityCalculateStates()
713 var states = base.AccessibilityCalculateStates();
715 states[AccessibilityState.Modal] = true;