Files
RobustToolbox/Robust.Client/UserInterface/Controls/MenuBar.cs
T
ThomasandGitHub 0a648ab619 More Cursor Usage in Controls (#6583)
* Add cursors for controls

Have TextEdot use NotAllowed instead of Arrow for Disabled

Make ScrollBar use {H,V}Resize

Make BaseButton use Pointer/NotAllowed instead of Arrow

Make ItemList use NotALlowed/Pointer/Arrow

Make MenuTopButton use Pointer

Make Slider use HResize

Fix SplitContainer not changing DefaultCursorShape if it changed
orientation

Make Tree use Pointer/Arrow

Make TabContainer use Pointer

* Fix ScrollBar

It needs to also change MouseMove because we don't have track
functionality yet and thus we only want to show interactivity when we're
directly hovering/grabbing the scrollbar.

* Address review
2026-05-25 18:14:44 +02:00

332 lines
9.6 KiB
C#

using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Numerics;
using Robust.Shared.Maths;
using static Robust.Client.UserInterface.Controls.BoxContainer;
namespace Robust.Client.UserInterface.Controls
{
/// <summary>
/// A good simple menu bar control, like the one at the top of your IDE!
/// </summary>
[Virtual]
public class MenuBar : PanelContainer
{
public const string StyleClassMenuBarPopup = "menuBarPopup";
private readonly List<Menu> _menus = new();
private readonly List<MenuTopButton> _buttons = new();
private readonly BoxContainer _hBox;
private readonly Popup _popup;
private readonly BoxContainer _popupVBox;
private bool _popupOpen;
private bool _isSubmenu = false;
public IList<Menu> Menus { get; }
public MenuBar() : this(false)
{
}
public MenuBar(bool isSubmenu = false)
{
_isSubmenu = isSubmenu;
_popup = new Popup
{
Children =
{
(_popupVBox = new BoxContainer
{
Orientation = LayoutOrientation.Vertical,
MinSize = new Vector2(300, 0)
})
},
StyleClasses = { StyleClassMenuBarPopup }
};
_popup.OnPopupHide += PopupHidden;
UserInterfaceManager.ModalRoot.AddChild(_popup);
Menus = new MenuCollection(this);
AddChild(_hBox = new BoxContainer
{
Orientation = LayoutOrientation.Horizontal,
SeparationOverride = 8
});
}
private void AddMenu(Menu menu)
{
MenuTopButton button = _isSubmenu ? new SubMenuTopButton(menu) : new MenuBarTopButton(menu);
_menus.Add(menu);
_buttons.Add(button);
_hBox.AddChild(button);
button.OnKeyBindDown += _ => OpenPopupFor(button);
button.OnMouseEntered += _ =>
{
if (_popupOpen)
{
OpenPopupFor(button);
}
};
}
private void OpenPopupFor(MenuTopButton button)
{
_popupVBox.RemoveAllChildren();
var menu = button.ChildMenu;
ConstructMenu(menu, _popupVBox);
var globalPos = button.GlobalPosition;
globalPos += new Vector2(_isSubmenu ? button.Width : 0, _isSubmenu ? 0 : button.Height);
_popup.Open(UIBox2.FromDimensions(globalPos, _popupVBox.Size));
// Set this after running open so that if this is called from MouseEntered,
// It won't get set to false by Open() closing the popup to move it.
_popupOpen = true;
}
private void PopupHidden()
{
_popupOpen = false;
}
private bool RemoveMenu(Menu menu)
{
var index = _menus.IndexOf(menu);
if (index < 0)
{
return false;
}
var button = _buttons[index];
_hBox.RemoveChild(button);
_buttons.RemoveAt(index);
_menus.RemoveAt(index);
return false;
}
private void ConstructMenu(Menu menu, Control container)
{
foreach (var entry in menu.Entries)
{
switch (entry)
{
case MenuButton menuButton:
var pushButton = new Button
{
Text = menuButton.Text,
ClipText = true,
Disabled = menuButton.Disabled,
TextAlign = Label.AlignMode.Left
};
pushButton.OnPressed += _ =>
{
_popup.Visible = false;
menuButton.OnPressed?.Invoke();
};
container.AddChild(pushButton);
break;
case MenuSeparator _:
var control = new Control {MinSize = new Vector2(0, 6)};
container.AddChild(control);
break;
case SubMenu submenuButton:
var subButton = new MenuBar(true);
container.AddChild(subButton);
container.OnChildRemoved += _ =>
{
if (subButton._popupOpen)
{
subButton._popup.RemoveAllChildren();
}
};
var subButtonMenu = new Menu(){
Title = submenuButton.Text
};
subButtonMenu.Entries.AddRange(submenuButton.Entries);
subButton.Menus.Add(subButtonMenu);
break;
}
}
}
private sealed class MenuCollection : IList<Menu>
{
private readonly MenuBar _menuBar;
public MenuCollection(MenuBar menuBar)
{
_menuBar = menuBar;
}
public IEnumerator<Menu> GetEnumerator()
{
return _menuBar._menus.GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
public void Add(Menu item)
{
_menuBar.AddMenu(item);
}
public void Clear()
{
foreach (var menu in this.ToList())
{
Remove(menu);
}
}
public bool Contains(Menu item)
{
return _menuBar._menus.Contains(item);
}
public void CopyTo(Menu[] array, int arrayIndex)
{
_menuBar._menus.CopyTo(array, arrayIndex);
}
public bool Remove(Menu item)
{
return _menuBar.RemoveMenu(item);
}
public int Count => _menuBar._menus.Count;
public bool IsReadOnly => false;
public int IndexOf(Menu item)
{
return _menuBar._menus.IndexOf(item);
}
public void Insert(int index, Menu item)
{
throw new NotImplementedException();
}
public void RemoveAt(int index)
{
Remove(this[index]);
}
public Menu this[int index]
{
get => _menuBar._menus[index];
set => throw new NotImplementedException();
}
}
public abstract class MenuTopButton : PanelContainer
{
public const string StylePseudoClassHover = "hover";
public Menu ChildMenu;
public MenuTopButton(Menu menu)
{
MouseFilter = MouseFilterMode.Pass;
DefaultCursorShape = CursorShape.Pointer;
ChildMenu = menu;
}
protected internal override void MouseEntered()
{
base.MouseEntered();
SetOnlyStylePseudoClass(StylePseudoClassHover);
}
protected internal override void MouseExited()
{
base.MouseExited();
SetOnlyStylePseudoClass(null);
}
}
public sealed class MenuBarTopButton : MenuTopButton
{
public Label Label { get; }
public MenuBarTopButton(Menu menu) : base(menu)
{
AddChild(Label = new Label {Text = menu.Title});
}
}
public sealed class SubMenuTopButton : MenuTopButton
{
public Button Button { get; } //basically just replicating the style of the other menu elements
public SubMenuTopButton(Menu menu) : base(menu)
{
Button = new Button
{
Text = menu.Title,
MinSize = new Vector2(300, 0),
MouseFilter = MouseFilterMode.Pass
};
Label arrowLabel = new Label {Text = ">", HorizontalAlignment = HAlignment.Right};
Button.AddChild(arrowLabel);
AddChild(Button);
}
}
/// <summary>
/// An entry in a menu of a menu bar.
/// </summary>
public abstract class MenuEntry
{
private protected MenuEntry()
{
}
}
/// <summary>
/// A menu in a menu bar, like file...
/// </summary>
public sealed class Menu
{
public string? Title { get; set; }
public List<MenuEntry> Entries { get; } = new();
}
/// <summary>
/// A basic button entry in a menu bar menu.
/// </summary>
public sealed class MenuButton : MenuEntry
{
public string? Text { get; set; }
public bool Disabled { get; set; }
public Action? OnPressed { get; set; }
}
public sealed class MenuSeparator : MenuEntry
{
}
public sealed class SubMenu : MenuEntry
{
public string? Text { get; set; }
public List<MenuEntry> Entries { get; set; } = new();
}
}
}