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 [EditorBrowsable(EditorBrowsableState.Never)]
63 protected override void Dispose(DisposeTypes type)
70 if (type == DisposeTypes.Explicit)
74 if (menuItems != null)
76 foreach (MenuItem menuItem in menuItems)
78 Content.Remove(menuItem);
82 Utility.Dispose(Content);
85 Utility.Dispose(Scrim);
90 Window.RemoveLayer(layer);
98 /// Applies style to MenuItem.
100 /// <param name="viewStyle">The style to apply.</param>
101 [EditorBrowsable(EditorBrowsableState.Never)]
102 public override void ApplyStyle(ViewStyle viewStyle)
104 styleApplied = false;
106 base.ApplyStyle(viewStyle);
108 menuStyle = viewStyle as MenuStyle;
109 if (menuStyle != null)
111 Content?.ApplyStyle(menuStyle.Content);
117 /// <summary>The Menu's relative position to Anchor.</summary>
118 /// <since_tizen> 9 </since_tizen>
119 public enum RelativePosition
122 /// At the start of the Anchor.
123 /// 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.
124 /// If this is used with <see cref="VerticalPositionToAnchor"/>, then Menu is positioned to the top of the Anchor.
126 /// <since_tizen> 9 </since_tizen>
129 /// At the center of the Anchor.
130 /// If this is used with <see cref="HorizontalPositionToAnchor"/> or <see cref="VerticalPositionToAnchor"/>, then Menu is positioned to the middle of the Anchor.
132 /// <since_tizen> 9 </since_tizen>
135 /// At the end of the Anchor.
136 /// 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.
137 /// If this is used with <see cref="VerticalPositionToAnchor"/>, then Menu is positioned to the bottom of the Anchor.
139 /// <since_tizen> 9 </since_tizen>
144 /// Menu items in Menu.
145 /// Menu items are not automatically disposed when Menu is disposed.
146 /// Therefore, please dispose Menu items when you dispose Menu.
148 /// <since_tizen> 9 </since_tizen>
149 public IEnumerable<MenuItem> Items
158 if (menuItems != null)
160 foreach (var oldItem in menuItems)
162 if (content.Children?.Contains(oldItem) == true)
164 content.Remove(oldItem);
171 if (menuItems == null)
176 foreach (var item in menuItems)
179 menuItemGroup.Add(item);
186 /// Menu is displayed at the anchor's position.
187 /// If there is not enough space to display menu at the anchor's position,
188 /// then menu is displayed at the proper position near anchor's position.
190 /// <since_tizen> 9 </since_tizen>
195 return GetValue(AnchorProperty) as View;
199 SetValue(AnchorProperty, value);
200 NotifyPropertyChanged();
203 private View InternalAnchor
223 CalculateSizeAndPosition();
228 /// The horizontal position of Menu relative to Anchor.
229 /// If Anchor is not set, then RelativePosition does not work.
230 /// If RelativePosition is Start, then Menu is displayed at the start of Anchor.
231 /// If RelativePosition is Center, then Menu is displayed at the center of Anchor.
232 /// If RelativePosition is End, then Menu is displayed at the end of Anchor.
233 /// If there is not enough space to display menu at the anchor's position,
234 /// then menu is displayed at the proper position near anchor's position.
236 /// <since_tizen> 9 </since_tizen>
237 public RelativePosition HorizontalPositionToAnchor
241 return (RelativePosition)GetValue(HorizontalPositionToAnchorProperty);
245 SetValue(HorizontalPositionToAnchorProperty, value);
246 NotifyPropertyChanged();
249 private RelativePosition InternalHorizontalPositionToAnchor
253 return horizontalPosition;
258 if (horizontalPosition == value)
263 horizontalPosition = value;
265 CalculateSizeAndPosition();
270 /// The vertical position of Menu relative to Anchor.
271 /// If Anchor is not set, then RelativePosition does not work.
272 /// If RelativePosition is Start, then Menu is displayed at the start of Anchor.
273 /// If RelativePosition is Center, then Menu is displayed at the center of Anchor.
274 /// If RelativePosition is End, then Menu is displayed at the end of Anchor.
275 /// If there is not enough space to display menu at the anchor's position,
276 /// then menu is displayed at the proper position near anchor's position.
278 /// <since_tizen> 9 </since_tizen>
279 public RelativePosition VerticalPositionToAnchor
283 return (RelativePosition)GetValue(VerticalPositionToAnchorProperty);
287 SetValue(VerticalPositionToAnchorProperty, value);
288 NotifyPropertyChanged();
291 private RelativePosition InternalVerticalPositionToAnchor
295 return verticalPosition;
300 if (verticalPosition == value)
305 verticalPosition = value;
307 CalculateSizeAndPosition();
314 /// <returns>The default Menu style.</returns>
315 [EditorBrowsable(EditorBrowsableState.Never)]
316 protected override ViewStyle CreateViewStyle()
318 return new MenuStyle();
324 [EditorBrowsable(EditorBrowsableState.Never)]
325 protected View Content
333 if (content == value)
353 content.RaiseAbove(Scrim);
360 /// Scrim is the screen region outside Menu.
361 /// If Scrim is touched, then Menu is dismissed.
363 [EditorBrowsable(EditorBrowsableState.Never)]
392 Content.RaiseAbove(scrim);
397 private Window Window
403 window = NUIApplication.GetDefaultWindow();
421 /// The Menu is displayed.
423 /// <param name="window">The Window where Menu is displayed.</param>
424 /// <since_tizen> 9 </since_tizen>
425 public void Post(Window window = null)
429 window = NUIApplication.GetDefaultWindow();
434 Window.AddLayer(layer);
437 CalculateSizeAndPosition();
438 RegisterDefaultLabel();
439 NotifyAccessibilityStatesChange(new AccessibilityStates(AccessibilityState.Visible, AccessibilityState.Showing), AccessibilityStatesNotifyMode.Recursive);
443 /// Dismiss the Menu.
444 /// The Menu becomes hidden and disposed.
446 /// <since_tizen> 9 </since_tizen>
447 public void Dismiss()
450 UnregisterDefaultLabel();
451 NotifyAccessibilityStatesChange(new AccessibilityStates(AccessibilityState.Visible, AccessibilityState.Showing), AccessibilityStatesNotifyMode.Recursive);
456 [EditorBrowsable(EditorBrowsableState.Never)]
457 public override void OnRelayout(Vector2 size, RelayoutContainer container)
459 base.OnRelayout(size, container);
461 CalculateSizeAndPosition();
464 private void Initialize()
466 Layout = new AbsoluteLayout();
468 WidthSpecification = LayoutParamPolicies.WrapContent;
469 HeightSpecification = LayoutParamPolicies.WrapContent;
471 BackgroundColor = Color.Transparent;
473 // Menu is added to Anchor so Menu should exclude layouting because
474 // if Anchor has Layout, then Menu is displayed at an incorrect position.
475 ExcludeLayouting = true;
477 Content = CreateDefaultContent();
478 if (styleApplied && (menuStyle != null))
480 Content.ApplyStyle(menuStyle.Content);
483 Scrim = CreateDefaultScrim();
485 menuItemGroup = new MenuItemGroup();
491 private ScrollableBase CreateDefaultContent()
493 return new ScrollableBase()
495 Layout = new LinearLayout()
497 LinearOrientation = LinearLayout.Orientation.Vertical,
499 ScrollingDirection = ScrollableBase.Direction.Vertical,
500 ScrollEnabled = true,
501 HideScrollbar = false,
502 ClippingMode = ClippingModeType.ClipChildren,
506 private View CreateDefaultScrim()
508 var scrim = new VisualView()
510 // Scrim is added to Menu so Scrim should exclude layouting
511 // not to enlarge Menu size.
512 ExcludeLayouting = true,
513 BackgroundColor = Color.Transparent,
514 Size = new Size(NUIApplication.GetDefaultWindow().Size),
517 scrim.TouchEvent += (object source, TouchEventArgs e) =>
519 if (e.Touch.GetState(0) == PointStateType.Up)
529 private void CalculateSizeAndPosition()
531 CalculateMenuPosition();
533 CalculateScrimPosition();
536 private View GetRootView()
539 View parent = GetParent() as View;
544 parent = parent?.GetParent() as View;
550 // Calculate menu's position based on Anchor and parent's positions.
551 // If there is not enought space, then menu's size can be also resized.
552 private void CalculateMenuPosition()
554 if ((Anchor == null) || (Content == null))
564 if (SizeWidth.Equals(0) && SizeHeight.Equals(0))
569 float menuScreenPosX = 0;
570 float menuScreenPosY = 0;
572 if (HorizontalPositionToAnchor == RelativePosition.Start)
574 if (GetRootView().LayoutDirection == ViewLayoutDirectionType.LTR)
576 menuScreenPosX = Anchor.ScreenPosition.X - SizeWidth;
580 menuScreenPosX = Anchor.ScreenPosition.X + Anchor.Margin.Start + Anchor.SizeWidth + Anchor.Margin.End;
583 else if (HorizontalPositionToAnchor == RelativePosition.Center)
585 menuScreenPosX = Anchor.ScreenPosition.X + Anchor.Margin.Start + (Anchor.SizeWidth / 2) - (SizeWidth / 2);
589 if (GetRootView().LayoutDirection == ViewLayoutDirectionType.LTR)
591 menuScreenPosX = Anchor.ScreenPosition.X + Anchor.Margin.Start + Anchor.SizeWidth + Anchor.Margin.End;
595 menuScreenPosX = Anchor.ScreenPosition.X - SizeWidth;
599 if (VerticalPositionToAnchor == RelativePosition.Start)
601 menuScreenPosY = Anchor.ScreenPosition.Y - SizeHeight;
603 else if (VerticalPositionToAnchor == RelativePosition.Center)
605 menuScreenPosY = Anchor.ScreenPosition.Y + Anchor.Margin.Top + (Anchor.SizeHeight / 2) - (SizeHeight / 2);
609 menuScreenPosY = Anchor.ScreenPosition.Y + Anchor.Margin.Top + Anchor.SizeHeight + Anchor.Margin.Bottom;
612 float menuSizeW = SizeWidth;
613 float menuSizeH = SizeHeight;
615 // Check if menu is not inside parent's boundary in x coordinate system.
616 if (menuScreenPosX + SizeWidth > Window.Size.Width)
618 if (HorizontalPositionToAnchor == RelativePosition.Center)
620 menuScreenPosX = Window.Size.Width - SizeWidth;
624 menuSizeW = Window.Size.Width - menuScreenPosX;
627 if (menuScreenPosX < 0)
631 if (menuSizeW > Window.Size.Width)
633 menuSizeW = Window.Size.Width;
637 // Check if menu is not inside parent's boundary in y coordinate system.
638 if (menuScreenPosY + SizeHeight > Window.Size.Height)
640 if (VerticalPositionToAnchor == RelativePosition.Center)
642 menuScreenPosY = Window.Size.Height - SizeHeight;
646 menuSizeH = Window.Size.Height - menuScreenPosY;
649 if (menuScreenPosY < 0)
653 if (menuSizeH > Window.Size.Height)
655 menuSizeH = Window.Size.Height;
659 // Position is relative to parent's coordinate system.
660 var menuPosX = menuScreenPosX;
661 var menuPosY = menuScreenPosY;
663 if (!PositionX.Equals(menuPosX) || !PositionY.Equals(menuPosY) || !SizeWidth.Equals(menuSizeW) || !SizeHeight.Equals(menuSizeH))
665 Position = new Position(menuPosX, menuPosY);
666 Size = new Size(menuSizeW, menuSizeH);
670 // Calculate scrim's position based on menu's position
671 private void CalculateScrimPosition()
678 // Menu's Position should be updated before doing this calculation.
679 if (!Scrim.PositionX.Equals(-PositionX) || !Scrim.PositionY.Equals(-PositionY))
681 Scrim.Position = new Position(-PositionX, -PositionY);
686 /// Initialize AT-SPI object.
688 [EditorBrowsable(EditorBrowsableState.Never)]
689 public override void OnInitialize()
692 AccessibilityRole = Role.PopupMenu;
696 /// Informs AT-SPI bridge about the set of AT-SPI states associated with this object.
698 [EditorBrowsable(EditorBrowsableState.Never)]
699 protected override AccessibilityStates AccessibilityCalculateStates()
701 var states = base.AccessibilityCalculateStates();
703 states[AccessibilityState.Modal] = true;