1
0
Fork 0
mirror of https://github.com/beefytech/Beef.git synced 2025-06-08 03:28:20 +02:00
Beef/BeefLibs/Beefy2D/src/widgets/Menu.bf

598 lines
17 KiB
Beef

using System;
using System.Collections;
using System.Diagnostics;
using System.Text;
using Beefy.theme;
using Beefy.events;
using Beefy.sys;
using Beefy.gfx;
using Beefy.geom;
namespace Beefy.widgets
{
public class MenuItemWidget : Widget
{
public Menu mMenuItem;
public MenuWidget mMenuWidget;
public int32 mIndex;
public bool mIsSelected;
public MenuWidget mSubMenu;
public this(Menu menuItem)
{
mMenuItem = menuItem;
}
public override void MouseEnter()
{
base.MouseEnter();
if ((!mWidgetWindow.mIsMouseMoving) || (mUpdateCnt == 0))
{
// We can get a MouseEnter from the widget moving to under the mouse, but we
// only want to respond to the mouse actually moving
return;
}
if (mMenuItem.mLabel != null)
{
mMenuWidget.SetSelection(mIndex);
if (!mWidgetWindow.mHasFocus)
{
var parentMenuItemWidget = mMenuWidget.mParentMenuItemWidget;
while (parentMenuItemWidget != null)
{
parentMenuItemWidget.mMenuWidget.mOpeningSubMenu = true;
parentMenuItemWidget = parentMenuItemWidget.mMenuWidget.mParentMenuItemWidget;
}
mWidgetWindow.SetForeground();
parentMenuItemWidget = mMenuWidget.mParentMenuItemWidget;
while (parentMenuItemWidget != null)
{
parentMenuItemWidget.mMenuWidget.mOpeningSubMenu = false;
parentMenuItemWidget = parentMenuItemWidget.mMenuWidget.mParentMenuItemWidget;
}
}
}
}
public override void MouseLeave()
{
base.MouseLeave();
if ((mWidgetWindow == null) || (!mWidgetWindow.mIsMouseMoving))
return;
if (mSubMenu == null)
mMenuWidget.SetSelection(-1);
}
public override void MouseUp(float x, float y, int32 btn)
{
base.MouseUp(x, y, btn);
//if ((mIsOver) && (mMouseClickHandler != null))
//mMouseClickHandler(this);
if (!mMouseOver)
mMenuWidget.Close();
}
public override void MouseDown(float x, float y, int32 btn, int32 btnCount)
{
base.MouseDown(x, y, btn, btnCount);
ReleaseMouseCapture();
}
public virtual void Submit()
{
}
}
public class MenuContainer : ScrollableWidget
{
public bool mReverse;
}
public class MenuWidget : Widget
{
public MenuItemWidget mParentMenuItemWidget;
public Menu mMenu;
//public BFWindowBase.Flags mWindowFlags = BFWindowBase.Flags.ClientSized | BFWindowBase.Flags.NoActivate | BFWindowBase.Flags.NoMouseActivate;
public BFWindowBase.Flags mWindowFlags = .ClientSized | .FakeFocus | .PopupPosition;
public int32 mSelectIdx = -1;
public Widget mRelativeWidget;
public List<MenuItemWidget> mItemWidgets = new List<MenuItemWidget>() ~ delete _;
public Menu mItemSelected;
public bool mOpeningSubMenu;
public bool mReplacing;
public bool mReverse;
public float mMinContainerWidth = 0;
public float mMaxContainerWidth = Int32.MaxValue;
public Insets mPopupInsets = new Insets() ~ delete _;
public bool mHasClosed;
public Event<Action<int>> mOnSelectionChanged ~ _.Dispose();
public bool mWasInitialized;
public this(Menu menu)
{
mMenu = menu;
}
public ~this()
{
// Only delete when we're the top-level menu
if (mMenu.mParent == null)
{
delete mMenu;
}
if ((!mHasClosed) && (mWasInitialized))
Close();
//Debug only
/*for (var call in WidgetWindow.sWindowMovedHandler.GetInvocationList())
{
if ((int)call.GetTarget() == (int)(void*)this)
{
Runtime.FatalError("MenuWidget not removed from sWindowMovedHandler!");
}
}*/
}
public virtual MenuContainer CreateMenuContainer()
{
return new MenuContainer();
}
public virtual void ResizeComponents()
{
}
public virtual void CalcSize()
{
}
public virtual MenuItemWidget CreateMenuItemWidget(Menu menuItem)
{
return new MenuItemWidget(menuItem);
}
public virtual void EnsureItemVisible(int itemIdx)
{
}
public virtual void SetSelection(int selIdx)
{
MarkDirty();
EnsureItemVisible(selIdx);
mSelectIdx = (int32)selIdx;
if ((mParentMenuItemWidget != null) && (selIdx != -1))
mParentMenuItemWidget.mMenuWidget.SetSelection(mParentMenuItemWidget.mIndex);
mOnSelectionChanged(selIdx);
}
public override void Resize(float x, float y, float width, float height)
{
base.Resize(x, y, width, height);
}
public virtual void CalcContainerSize(MenuContainer menuContainer, bool allowScrollable, ref float screenX, ref float screenY, out float width, out float height)
{
Rect menuRect = menuContainer.CalcRectFromContent();
width = Math.Min(mMaxContainerWidth, Math.Max(mMinContainerWidth, menuRect.mWidth));
height = menuRect.mHeight;
int workspaceX;
int workspaceY;
int workspaceWidth;
int workspaceHeight;
BFApp.sApp.GetWorkspaceRect(out workspaceX, out workspaceY, out workspaceWidth, out workspaceHeight);
float maxY = workspaceY + workspaceHeight;
if ((!allowScrollable) && (screenX + width > workspaceWidth))
screenX = screenX - width + mPopupInsets.mRight;
else
screenX -= mPopupInsets.mLeft;
if (mReverse)
{
maxY = screenY;
screenY = Math.Max(workspaceY, screenY - height);
}
if ((!allowScrollable) && (screenY + height > maxY))
screenY = screenY - height + mPopupInsets.mBottom;
else
{
screenY -= mPopupInsets.mTop;
if (screenY + height > maxY)
{
menuContainer.InitScrollbars(false, true);
height = maxY - screenY;
}
else
menuContainer.InitScrollbars(false, false);
menuRect = menuContainer.CalcRectFromContent();
width = Math.Min(mMaxContainerWidth, Math.Max(mMinContainerWidth, menuRect.mWidth));
}
}
public virtual float GetReverseAdjust()
{
return 10;
}
public virtual void Init(Widget relativeWidget, float x, float y, bool allowScrollable = false, WidgetWindow widgetWindow = null)
{
mWasInitialized = true;
float curY = y;
WidgetWindow curWidgetWindow = widgetWindow;
bool reverse = false;
bool allowReverse = true;
if ((allowReverse) && (relativeWidget != null))
{
if (mHeight == 0)
CalcSize();
float minHeight;
if (allowScrollable)
minHeight = Math.Min(mHeight, 200) + 8;
else
minHeight = mHeight;
float screenX;
float screenY;
relativeWidget.SelfToRootTranslate(0, curY, out screenX, out screenY);
screenY += relativeWidget.mWidgetWindow.mClientY;
int wsX;
int wsY;
int wsWidth;
int wsHeight;
BFApp.sApp.GetWorkspaceRect(out wsX, out wsY, out wsWidth, out wsHeight);
float spaceLeft = (wsY + wsHeight) - (screenY);
if (spaceLeft < minHeight)
{
curY += GetReverseAdjust();
reverse = true;
}
}
mReverse = reverse;
int32 idx = 0;
for (Menu item in mMenu.mItems)
{
MenuItemWidget itemWidget = CreateMenuItemWidget(item);
itemWidget.mIndex = idx;
itemWidget.mMenuWidget = this;
mItemWidgets.Add(itemWidget);
AddWidget(itemWidget);
++idx;
}
float screenX = x;
float screenY = curY;
CalcSize();
if (relativeWidget != null)
relativeWidget.SelfToRootTranslate(x, curY, out screenX, out screenY);
screenX += relativeWidget.mWidgetWindow.mClientX;
screenY += relativeWidget.mWidgetWindow.mClientY;
MenuContainer menuContainer;
if (curWidgetWindow == null)
{
menuContainer = CreateMenuContainer();
menuContainer.mReverse = reverse;
menuContainer.mScrollContent = this;
menuContainer.mScrollContentContainer.AddWidget(this);
float screenWidth;
float screenHeight;
CalcContainerSize(menuContainer, allowScrollable, ref screenX, ref screenY, out screenWidth, out screenHeight);
WidgetWindow parentWindow = (relativeWidget != null) ? relativeWidget.mWidgetWindow : null;
curWidgetWindow = new WidgetWindow(parentWindow,
"Popup",
(int32)(screenX), (int32)(screenY),
(int32)screenWidth, (int32)screenHeight,
mWindowFlags,
menuContainer);
if (parentWindow != null)
{
parentWindow.TransferMouse(curWidgetWindow);
}
//Resize(0, 0, mWidth, mHeight);
menuContainer.UpdateScrollbars();
mRelativeWidget = relativeWidget;
}
else
{
menuContainer = (MenuContainer)curWidgetWindow.mRootWidget;
menuContainer.mReverse = reverse;
var oldMenuWidget = (MenuWidget)menuContainer.mScrollContent;
oldMenuWidget.mReplacing = true;
oldMenuWidget.RemoveSelf();
oldMenuWidget.Close();
delete oldMenuWidget;
menuContainer.mScrollContent = this;
menuContainer.AddWidget(this);
float screenWidth;
float screenHeight;
CalcContainerSize(menuContainer, allowScrollable, ref screenX, ref screenY, out screenWidth, out screenHeight);
curWidgetWindow.Resize((int32)(screenX), (int32)(screenY),
(int32)screenWidth, (int32)screenHeight);
}
menuContainer.mScrollContent.mWidth = menuContainer.mScrollContentContainer.mWidth;
ResizeComponents();
WidgetWindow.sOnKeyDown.Add(new => HandleKeyDown);
WidgetWindow.sOnWindowLostFocus.Add(new => WindowLostFocus);
WidgetWindow.sOnMouseDown.Add(new => HandleMouseDown);
WidgetWindow.sOnMouseWheel.Add(new => HandleMouseWheel);
WidgetWindow.sOnWindowMoved.Add(new => HandleWindowMoved);
WidgetWindow.sOnMenuItemSelected.Add(new => HandleSysMenuItemSelected);
if (mRelativeWidget != null)
{
mRelativeWidget.mOnRemovedFromParent.Add(new => HandleRemovedFromManager);
mRelativeWidget.mOnMouseUp.Add(new => HandleMouseUp);
}
curWidgetWindow.SetFocus(this);
}
public void SubmitSelection()
{
if (mSelectIdx == -1)
return;
//mItemWidgets[mSelectIdx].MouseClicked(0, 0, 0);
mItemWidgets[mSelectIdx].Submit();
}
void HandleRemovedFromManager(Widget widget, Widget prevParent, WidgetWindow widgetWindow)
{
Close();
}
void HandleMouseUp(MouseEvent theEvent)
{
if ((mSelectIdx != -1) && (theEvent.mBtn == 0))
{
//SubmitSelection();
}
}
void HandleMouseWheel(MouseEvent theEvent)
{
HandleMouseDown(theEvent);
}
void HandleMouseDown(MouseEvent theEvent)
{
WidgetWindow widgetWindow = (WidgetWindow)theEvent.mSender;
if (!(widgetWindow.mRootWidget is MenuContainer))
Close();
}
void HandleSysMenuItemSelected(IMenu sysMenu)
{
Close();
}
bool IsMenuWindow(BFWindow window)
{
var newWidgetWindow = window as WidgetWindow;
if (newWidgetWindow != null)
{
if (newWidgetWindow.mRootWidget is MenuContainer)
return true;
}
return false;
}
void WindowLostFocus(BFWindow window, BFWindow newFocus)
{
if (mOpeningSubMenu)
return;
if (IsMenuWindow(newFocus))
return;
if ((mWidgetWindow != null) && (!mWidgetWindow.mHasFocus))
Close();
}
void HandleKeyDown(KeyDownEvent evt)
{
if (evt.mKeyCode == KeyCode.Escape)
{
evt.mHandled = true;
Close();
}
}
void HandleWindowMoved(BFWindow window)
{
if (IsMenuWindow(window))
return;
Close();
}
public void RemoveHandlers()
{
WidgetWindow.sOnMouseDown.Remove(scope => HandleMouseDown, true);
WidgetWindow.sOnMouseWheel.Remove(scope => HandleMouseWheel, true);
WidgetWindow.sOnWindowLostFocus.Remove(scope => WindowLostFocus, true);
WidgetWindow.sOnWindowMoved.Remove(scope => HandleWindowMoved, true);
WidgetWindow.sOnMenuItemSelected.Remove(scope => HandleSysMenuItemSelected, true);
WidgetWindow.sOnKeyDown.Remove(scope => HandleKeyDown, true);
if (mRelativeWidget != null)
{
mRelativeWidget.mOnRemovedFromParent.Remove(scope => HandleRemovedFromManager, true);
mRelativeWidget.mOnMouseUp.Remove(scope => HandleMouseUp, true);
}
}
public void Close()
{
if (mHasClosed)
return;
mHasClosed = true;
RemoveHandlers();
//Debug.Assert(mWidgetWindow != null);
if (mWidgetWindow != null)
{
mWidgetWindow.Close();
if (mMenu.mOnMenuClosed.HasListeners)
mMenu.mOnMenuClosed(mMenu, mItemSelected);
}
}
public override void LostFocus()
{
base.LostFocus();
if (mOpeningSubMenu)
return;
if (!mReplacing)
{
Close();
}
else
{
RemoveHandlers();
}
}
public override void KeyDown(KeyCode keyCode, bool isRepeat)
{
base.KeyDown(keyCode, isRepeat);
if (mItemWidgets.Count > 0)
{
int32 itemsPerPage = (int32)Math.Ceiling((mParent.mHeight - 8) / mItemWidgets[0].mHeight) - 1;
switch (keyCode)
{
case .Home:
SetSelection(0);
break;
case .End:
SetSelection(mItemWidgets.Count - 1);
break;
case .PageUp:
SetSelection(Math.Max(0, mSelectIdx - itemsPerPage));
break;
case .PageDown:
SetSelection(Math.Min(mItemWidgets.Count - 1, Math.Max(0, mSelectIdx) + itemsPerPage));
break;
case .Up:
if (mSelectIdx == -1)
{
SetSelection(0);
break;
}
else if (mSelectIdx > 0)
SetSelection(mSelectIdx - 1);
break;
case .Down:
if (mSelectIdx == -1)
{
SetSelection(0);
break;
}
else if (mSelectIdx < mItemWidgets.Count - 1)
SetSelection(mSelectIdx + 1);
break;
case .Return:
SubmitSelection();
case .Right:
if (mItemSelected != null)
{
if (!mItemSelected.mItems.IsEmpty)
{
}
}
default:
}
}
}
}
public class Menu : IMenu
{
public Menu mParent;
public String mLabel ~ delete _;
public List<Menu> mItems = new List<Menu>() ~ DeleteContainerAndItems!(_);
public Event<delegate void(Menu menu)> mOnMenuItemSelected ~ _.Dispose();
public Event<delegate void(Menu menu)> mOnMenuItemUpdate ~ _.Dispose();
public Event<delegate void(Menu menu, Menu itemSelected)> mOnMenuClosed ~ _.Dispose();
public Object mThemeData;
public bool mDisabled;
public bool mBold;
public IDrawable mIconImage;
public bool mForceParent;
public ~this()
{
}
public bool IsEmpty
{
get
{
return mItems.IsEmpty;
}
}
public bool IsParent
{
get
{
return !mItems.IsEmpty || mForceParent;
}
set
{
mForceParent = value;
}
}
public virtual Menu AddItem(StringView label = default)
{
Menu item = new Menu();
item.mParent = this;
if (!label.IsEmpty)
item.mLabel = new String(label);
mItems.Add(item);
return item;
}
public void SetDisabled(bool disabled)
{
mDisabled = disabled;
}
}
}