mirror of
https://github.com/beefytech/Beef.git
synced 2025-06-28 20:46:00 +02:00
608 lines
20 KiB
Beef
608 lines
20 KiB
Beef
using System;
|
|
using System.Collections;
|
|
using System.Text;
|
|
using System.Diagnostics;
|
|
using Beefy.gfx;
|
|
using Beefy.theme;
|
|
|
|
namespace Beefy.widgets
|
|
{
|
|
public abstract class TabbedView : DockedWidget
|
|
{
|
|
public class TabButton : Widget, IDockable, IDragInterface
|
|
{
|
|
public bool mIsActive;
|
|
public String mLabel ~ delete _;
|
|
public TabbedView mTabbedView;
|
|
public WidgetWindow mNewDraggingWindow;
|
|
public WidgetWindow mSrcDraggingWindow;
|
|
public float mWindowDragRelX;
|
|
public float mWindowDragRelY;
|
|
public float mLastMouseX;
|
|
public float mLastMouseY;
|
|
public bool mOwnsContent = true;
|
|
public Widget mContent;
|
|
public Insets mContentInsets = new Insets() ~ delete _;
|
|
public DragHelper mDragHelper ~ delete _;
|
|
public Event<Action> mCloseClickedEvent ~ _.Dispose();
|
|
|
|
public float mWantWidth;
|
|
public WidgetWindow mMouseDownWindow;
|
|
|
|
public String Label
|
|
{
|
|
get
|
|
{
|
|
return mLabel;
|
|
}
|
|
|
|
set
|
|
{
|
|
String.NewOrSet!(mLabel, value);
|
|
}
|
|
}
|
|
|
|
public this()
|
|
{
|
|
mDragHelper = new DragHelper(this, this);
|
|
}
|
|
|
|
public ~this()
|
|
{
|
|
Debug.Assert(mMouseDownWindow == null);
|
|
|
|
if ((mContent != null) && (mOwnsContent))
|
|
{
|
|
if (mContent.mParent != null)
|
|
mContent.RemoveSelf();
|
|
delete mContent;
|
|
}
|
|
}
|
|
|
|
public override void RehupScale(float oldScale, float newScale)
|
|
{
|
|
float valScale = newScale / oldScale;
|
|
mContentInsets.Scale(valScale);
|
|
//Utils.SnapScale(ref mWidth, valScale);
|
|
mWantWidth *= valScale;
|
|
//Utils.SnapScale(ref mHeight, valScale);
|
|
base.RehupScale(oldScale, newScale);
|
|
}
|
|
|
|
public virtual void Activate(bool setFocus = true)
|
|
{
|
|
TabButton button = mTabbedView.GetActiveTab();
|
|
if (button != this)
|
|
{
|
|
if (button != null)
|
|
button.Deactivate();
|
|
mIsActive = true;
|
|
mTabbedView.mNeedResizeTabs = true;
|
|
|
|
mTabbedView.AddWidget(mContent);
|
|
if ((setFocus) && (mWidgetWindow != null))
|
|
mContent.SetFocus();
|
|
ResizeContent();
|
|
}
|
|
else if ((setFocus) && (mWidgetWindow != null))
|
|
mContent.SetFocus();
|
|
}
|
|
|
|
public virtual void ResizeContent()
|
|
{
|
|
mContent.Resize(mContentInsets.mLeft, mTabbedView.mTabHeight + mContentInsets.mTop,
|
|
Math.Max(mTabbedView.mWidth - mContentInsets.mLeft - mContentInsets.mRight, 0),
|
|
Math.Max(mTabbedView.mHeight - mTabbedView.mTabHeight - mContentInsets.mTop - mContentInsets.mBottom, 0));
|
|
}
|
|
|
|
public virtual void Deactivate()
|
|
{
|
|
if (mIsActive)
|
|
{
|
|
mIsActive = false;
|
|
mTabbedView.RemoveWidget(mContent);
|
|
}
|
|
}
|
|
|
|
public override void MouseDown(float x, float y, int32 btn, int32 btnCount)
|
|
{
|
|
if (btn == 2)
|
|
{
|
|
mCloseClickedEvent();
|
|
return;
|
|
}
|
|
|
|
bool wasMouseDown = mMouseDown;
|
|
base.MouseDown(x, y, btn, btnCount);
|
|
Activate();
|
|
if ((mMouseDown) && (!wasMouseDown))
|
|
{
|
|
//Debug.WriteLine("MouseDown {0} {1}", this, mWidgetWindow);
|
|
mMouseDownWindow = mWidgetWindow;
|
|
mMouseDownWindow.mOnMouseLeftWindow.Add(new => MouseLeftWindow);
|
|
}
|
|
}
|
|
|
|
public override void MouseMove(float x, float y)
|
|
{
|
|
base.MouseMove(x, y);
|
|
|
|
mLastMouseX = x;
|
|
mLastMouseY = y;
|
|
|
|
if (mNewDraggingWindow != null)
|
|
{
|
|
float rootX;
|
|
float rootY;
|
|
SelfToRootTranslate(mLastMouseX, mLastMouseY, out rootX, out rootY);
|
|
|
|
mNewDraggingWindow.SetClientPosition(
|
|
(int32)(mSrcDraggingWindow.mClientX + rootX - mWindowDragRelX), (int32)(mSrcDraggingWindow.mClientY + rootY - mWindowDragRelY));
|
|
}
|
|
}
|
|
|
|
public void DragStart()
|
|
{
|
|
mSrcDraggingWindow = mWidgetWindow;
|
|
|
|
if ((IsTotalWindowContent()) && (!mWidgetWindow.mIsMainWindow))
|
|
{
|
|
// Drag around entire window if we are the only content in it
|
|
SelfToRootTranslate(mDragHelper.mMouseDownX, mDragHelper.mMouseDownY, out mWindowDragRelX, out mWindowDragRelY);
|
|
mNewDraggingWindow = mWidgetWindow;
|
|
mNewDraggingWindow.SetAlpha(0.5f, 0, false);
|
|
mNewDraggingWindow.mOnWindowLostFocus.Add(new => WindowDragLostFocusHandler);
|
|
}
|
|
}
|
|
|
|
public void DragEnd()
|
|
{
|
|
//mWidgetWindow.mMouseLeftWindowDelegate.Remove(scope => MouseLeftWindow, true);
|
|
|
|
if ((mSrcDraggingWindow != null) && (mSrcDraggingWindow.mCaptureWidget != null))
|
|
mSrcDraggingWindow.ReleaseMouseCaptures();
|
|
|
|
mTabbedView.mParentDockingFrame?.GetRootDockingFrame().HideDragTarget(this, !mDragHelper.mAborted);
|
|
if (mNewDraggingWindow != null)
|
|
{
|
|
mNewDraggingWindow.mOnWindowLostFocus.Remove(scope => WindowDragLostFocusHandler, true);
|
|
DockingFrame dockingFrame = (DockingFrame)mNewDraggingWindow.mRootWidget;
|
|
if (dockingFrame.GetDockedWindowCount() > 0)
|
|
mNewDraggingWindow.SetAlpha(1.0f, 0, true);
|
|
mNewDraggingWindow = null;
|
|
}
|
|
mSrcDraggingWindow = null;
|
|
}
|
|
|
|
public void MouseDrag(float x, float y, float dX, float dY)
|
|
{
|
|
mTabbedView.mParentDockingFrame?.GetRootDockingFrame().ShowDragTarget(this);
|
|
}
|
|
|
|
public override void MouseUp(float x, float y, int32 btn)
|
|
{
|
|
bool wasMouseDown = mMouseDown;
|
|
base.MouseUp(x, y, btn);
|
|
if ((wasMouseDown) && (!mMouseDown))
|
|
{
|
|
mMouseDownWindow.mOnMouseLeftWindow.Remove(scope => MouseLeftWindow, true);
|
|
mMouseDownWindow = null;
|
|
}
|
|
}
|
|
|
|
public override void RemovedFromParent(Widget previousParent, WidgetWindow window)
|
|
{
|
|
base.RemovedFromParent(previousParent, window);
|
|
|
|
}
|
|
|
|
public virtual bool IsTotalWindowContent()
|
|
{
|
|
if (mTabbedView.mParentDockingFrame == null)
|
|
return false;
|
|
return (mTabbedView.mParentDockingFrame.mParentDockingFrame == null) &&
|
|
(mTabbedView.mParentDockingFrame.GetDockedWindowCount() == 1) &&
|
|
(mTabbedView.GetTabCount() == 1) &&
|
|
mTabbedView.mAutoClose;
|
|
}
|
|
|
|
void WindowDragLostFocusHandler(BFWindow window, BFWindow newFocus)
|
|
{
|
|
mDragHelper.CancelDrag();
|
|
}
|
|
|
|
public void MouseLeftWindow(BFWindow window)
|
|
{
|
|
if (mDragHelper.mIsDragging)
|
|
{
|
|
if (mNewDraggingWindow == null)
|
|
{
|
|
mDragHelper.SetPreparingForWidgetMove(true);
|
|
|
|
WidgetWindow prevWidgetWindow = mWidgetWindow;
|
|
mTabbedView.mParentDockingFrame.GetRootDockingFrame().HideDragTarget(this);
|
|
|
|
mWindowDragRelX = mDragHelper.mMouseDownX;
|
|
mWindowDragRelY = mDragHelper.mMouseDownY;
|
|
|
|
float rootX;
|
|
float rootY;
|
|
SelfToRootTranslate(mLastMouseX, mLastMouseY, out rootX, out rootY);
|
|
|
|
DockingFrame subFrame = ThemeFactory.mDefault.CreateDockingFrame();
|
|
|
|
var parentWindow = mWidgetWindow;
|
|
while (parentWindow.mParent != null)
|
|
parentWindow = (WidgetWindow)parentWindow.mParent;
|
|
|
|
mNewDraggingWindow = new WidgetWindow(parentWindow, "",
|
|
(int32)(mSrcDraggingWindow.mClientX + rootX - mDragHelper.mMouseDownX), (int32)(mSrcDraggingWindow.mClientY + rootY - mDragHelper.mMouseDownY),
|
|
300, 500,
|
|
BFWindowBase.Flags.Border | BFWindowBase.Flags.ThickFrame | BFWindowBase.Flags.Resizable | BFWindowBase.Flags.SysMenu |
|
|
BFWindowBase.Flags.Caption | BFWindowBase.Flags.Minimize | BFWindowBase.Flags.ToolWindow | BFWindowBase.Flags.TopMost |
|
|
BFWindowBase.Flags.UseParentMenu | BFWindowBase.Flags.Maximize,
|
|
subFrame);
|
|
Dock(subFrame, null, DockingFrame.WidgetAlign.Top);
|
|
//subFrame.AddDockedWidget(fourthTabbedView, null, DockingFrame.WidgetAlign.Left, false);
|
|
|
|
prevWidgetWindow.SetNonExclusiveMouseCapture();
|
|
mNewDraggingWindow.SetAlpha(0.5f, 0, false);
|
|
//mNewDraggingWindow.SetMouseCapture();
|
|
//mNewDraggingWindow.mCaptureWidget = sub
|
|
mDragHelper.SetPreparingForWidgetMove(false);
|
|
|
|
// We need the previous window to keep tracking the mouse dragging, so we need to restore mCaptureWidget since it would be removed
|
|
// when the widget gets removed from the previous parent
|
|
prevWidgetWindow.mCaptureWidget = this;
|
|
|
|
mNewDraggingWindow.mOnWindowLostFocus.Add(new => WindowDragLostFocusHandler);
|
|
|
|
if (mTabbedView.mSharedData.mOpenNewWindowDelegate.HasListeners)
|
|
mTabbedView.mSharedData.mOpenNewWindowDelegate(mTabbedView, mNewDraggingWindow);
|
|
}
|
|
}
|
|
}
|
|
|
|
public override void Update()
|
|
{
|
|
base.Update();
|
|
if (mNewDraggingWindow != null)
|
|
{
|
|
if (!mNewDraggingWindow.mHasFocus)
|
|
{
|
|
//int a = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
public override void MouseLeave()
|
|
{
|
|
base.MouseLeave();
|
|
|
|
}
|
|
|
|
public bool CanDock(DockingFrame frame, DockedWidget refWidget, DockingFrame.WidgetAlign align)
|
|
{
|
|
return (align != DockingFrame.WidgetAlign.Inside) || (refWidget is TabbedView);
|
|
}
|
|
|
|
public virtual void Dock(DockingFrame frame, DockedWidget refWidget, DockingFrame.WidgetAlign align)
|
|
{
|
|
if ((refWidget != null) && (refWidget.mWidgetWindow != mWidgetWindow) && (mWidgetWindow != null))
|
|
mWidgetWindow.SetForeground();
|
|
|
|
if ((mTabbedView.GetTabCount() == 1) && mTabbedView.mAutoClose)
|
|
{
|
|
mTabbedView.Dock(frame, refWidget, align);
|
|
return;
|
|
}
|
|
|
|
TabbedView prevTabbedView = mTabbedView;
|
|
|
|
frame.StartContentInterpolate();
|
|
|
|
if (align == DockingFrame.WidgetAlign.Inside)
|
|
{
|
|
TabbedView tabbedView = (TabbedView)refWidget;
|
|
|
|
if (tabbedView != mTabbedView)
|
|
{
|
|
mTabbedView.RemoveTab(this, false);
|
|
tabbedView.AddTab(this, tabbedView.GetInsertPositionFromCursor());
|
|
Activate();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Create new tabbed view to put this in
|
|
TabbedView tabbedView = mTabbedView.CreateTabbedView(mTabbedView.mSharedData);
|
|
//tabbedView.mSharedData = mTabbedView.mSharedData.Ref();
|
|
//tabbedView.mSharedData.mOpenNewWindowDelegate = mTabbedView.mSharedData.mOpenNewWindowDelegate;
|
|
tabbedView.SetRequestedSize(mTabbedView.mWidth, mTabbedView.mHeight);
|
|
mTabbedView.RemoveTab(this, false);
|
|
tabbedView.AddTab(this, 0);
|
|
|
|
float rootX;
|
|
float rootY;
|
|
prevTabbedView.SelfToRootTranslate(mX, mY, out rootX, out rootY);
|
|
|
|
tabbedView.StartInterpolate(rootX, rootY, mWidth, mHeight);
|
|
tabbedView.Dock(frame, refWidget, align);
|
|
}
|
|
|
|
if ((prevTabbedView.GetTabCount() == 0) && prevTabbedView.mAutoClose)
|
|
{
|
|
prevTabbedView.mParentDockingFrame.RemoveDockedWidget(prevTabbedView);
|
|
}
|
|
}
|
|
|
|
public virtual void DrawDockPreview(Graphics g)
|
|
{
|
|
using (g.PushTranslate(-mX - mDragHelper.mMouseDownX, -mY - mDragHelper.mMouseDownY))
|
|
mTabbedView.DrawDockPreview(g);
|
|
}
|
|
}
|
|
|
|
public class SharedData
|
|
{
|
|
int32 mRefCount = 1;
|
|
|
|
public Event<OpenNewWindowDelegate> mOpenNewWindowDelegate ~ _.Dispose();
|
|
public Event<delegate void(TabbedView)> mTabbedViewClosed ~ _.Dispose();
|
|
|
|
public SharedData Ref()
|
|
{
|
|
mRefCount++;
|
|
return this;
|
|
}
|
|
|
|
public void Deref()
|
|
{
|
|
if (--mRefCount == 0)
|
|
delete this;
|
|
}
|
|
}
|
|
|
|
public delegate void OpenNewWindowDelegate(TabbedView tabbedView, WidgetWindow newWindow);
|
|
|
|
public delegate void(Menu) mPopulateMenuEvent;
|
|
public float mTabHeight;
|
|
public float mTabAreaWidth;
|
|
public bool mNeedResizeTabs;
|
|
public SharedData mSharedData ~ _.Deref();
|
|
|
|
public List<TabButton> mTabs = new List<TabButton>() ~ delete _;
|
|
|
|
public this(SharedData sharedData)
|
|
{
|
|
if (sharedData != null)
|
|
mSharedData = sharedData.Ref();
|
|
else
|
|
mSharedData = new SharedData();
|
|
}
|
|
|
|
public ~this()
|
|
{
|
|
for (var tab in mTabs)
|
|
Widget.RemoveAndDelete(tab);
|
|
}
|
|
|
|
public virtual TabbedView CreateTabbedView(TabbedView.SharedData sharedData)
|
|
{
|
|
return ThemeFactory.mDefault.CreateTabbedView(sharedData);
|
|
}
|
|
|
|
public void CloseTabs(bool autoClose, bool closeCurrent)
|
|
{
|
|
let prevAutoClose = mAutoClose;
|
|
mAutoClose = autoClose;
|
|
var tabs = scope List<TabButton>();
|
|
for (var tab in mTabs)
|
|
tabs.Add(tab);
|
|
|
|
if (tabs.IsEmpty)
|
|
{
|
|
if (autoClose)
|
|
{
|
|
if (var dockingFrame = mParent as DockingFrame)
|
|
{
|
|
dockingFrame.RemoveDockedWidget(this);
|
|
BFApp.sApp.DeferDelete(this);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
for (var tab in tabs)
|
|
{
|
|
if ((!closeCurrent) && (tab.mIsActive))
|
|
continue;
|
|
tab.mCloseClickedEvent();
|
|
}
|
|
}
|
|
mAutoClose = prevAutoClose;
|
|
}
|
|
|
|
public void Closed()
|
|
{
|
|
mSharedData.mTabbedViewClosed(this);
|
|
}
|
|
|
|
public override void RehupScale(float oldScale, float newScale)
|
|
{
|
|
float valScale = newScale / oldScale;
|
|
Utils.RoundScale(ref mTabHeight, valScale);
|
|
Utils.RoundScale(ref mTabAreaWidth, valScale);
|
|
base.RehupScale(oldScale, newScale);
|
|
mNeedResizeTabs = true;
|
|
}
|
|
|
|
public virtual TabButton FindTabForContent(Widget content)
|
|
{
|
|
for (TabButton aTabButton in mTabs)
|
|
if (aTabButton.mContent == content)
|
|
return aTabButton;
|
|
return null;
|
|
}
|
|
|
|
protected virtual TabButton CreateTabButton()
|
|
{
|
|
return new TabButton();
|
|
}
|
|
|
|
public virtual int GetTabCount()
|
|
{
|
|
return mTabs.Count;
|
|
}
|
|
|
|
public virtual void WithTabs(delegate void(TabbedView.TabButton) func)
|
|
{
|
|
for (var tab in mTabs)
|
|
func(tab);
|
|
}
|
|
|
|
public virtual TabButton GetActiveTab()
|
|
{
|
|
TabButton activeTab = null;
|
|
WithTabs(scope [&] (tab) =>
|
|
{
|
|
if (tab.mIsActive)
|
|
activeTab = tab;
|
|
});
|
|
return activeTab;
|
|
}
|
|
|
|
public virtual TabButton AddTab(String label, float width, Widget content, bool ownsContent, int insertIdx)
|
|
{
|
|
TabButton aTabButton = CreateTabButton();
|
|
aTabButton.mTabbedView = this;
|
|
aTabButton.mOwnsContent = ownsContent;
|
|
aTabButton.Label = label;
|
|
aTabButton.mWantWidth = width;
|
|
aTabButton.mHeight = mTabHeight;
|
|
aTabButton.mContent = content;
|
|
AddTab(aTabButton, insertIdx);
|
|
return aTabButton;
|
|
}
|
|
|
|
public virtual int GetInsertPositionFromCursor()
|
|
{
|
|
int bestIdx = mTabs.Count;
|
|
while (bestIdx > 0)
|
|
{
|
|
var checkTab = mTabs[bestIdx - 1];
|
|
float tabCenterX;
|
|
float tabCenterY;
|
|
checkTab.SelfToRootTranslate(checkTab.mWidth / 2, checkTab.mHeight / 2, out tabCenterX, out tabCenterY);
|
|
if (mWidgetWindow.mMouseX > tabCenterX)
|
|
break;
|
|
bestIdx--;
|
|
}
|
|
return bestIdx;
|
|
}
|
|
|
|
public virtual void AddTab(TabButton tabButton, int insertIdx)
|
|
{
|
|
AddWidget(tabButton);
|
|
mTabs.Insert(insertIdx, tabButton);
|
|
tabButton.mTabbedView = this;
|
|
if (mTabs.Count == 1)
|
|
tabButton.Activate();
|
|
mNeedResizeTabs = true;
|
|
}
|
|
|
|
public virtual void RemoveTab(TabButton tabButton, bool deleteTab = true)
|
|
{
|
|
bool hadFocus = mWidgetWindow.mFocusWidget != null;
|
|
if (tabButton.mIsActive)
|
|
tabButton.Deactivate();
|
|
RemoveWidget(tabButton);
|
|
mTabs.Remove(tabButton);
|
|
if ((GetActiveTab() == null) && (mTabs.Count > 0))
|
|
mTabs[0].Activate((hadFocus) && (mWidgetWindow.mFocusWidget == null));
|
|
mNeedResizeTabs = true;
|
|
|
|
if (deleteTab)
|
|
BFApp.sApp.DeferDelete(tabButton);
|
|
}
|
|
|
|
protected virtual void ResizeTabs(bool immediate)
|
|
{
|
|
float curX = 0;
|
|
for (TabButton aTabButton in mTabs)
|
|
{
|
|
aTabButton.Resize(curX, aTabButton.mY, aTabButton.mWidth, aTabButton.mHeight);
|
|
curX += aTabButton.mWidth;
|
|
}
|
|
|
|
mTabAreaWidth = curX;
|
|
mNeedResizeTabs = false;
|
|
}
|
|
|
|
public void FinishTabAnim()
|
|
{
|
|
if (mNeedResizeTabs)
|
|
{
|
|
ResizeTabs(true);
|
|
}
|
|
}
|
|
|
|
public override void Update()
|
|
{
|
|
base.Update();
|
|
if (mNeedResizeTabs)
|
|
ResizeTabs(false);
|
|
}
|
|
|
|
public override void Dock(DockingFrame frame, DockedWidget refWidget, DockingFrame.WidgetAlign align)
|
|
{
|
|
if (this == refWidget)
|
|
return;
|
|
|
|
if ((refWidget != null) && (refWidget.mWidgetWindow != mWidgetWindow))
|
|
refWidget.mWidgetWindow.SetForeground();
|
|
|
|
if (align == DockingFrame.WidgetAlign.Inside)
|
|
{
|
|
TabbedView tabbedView = (TabbedView)refWidget;
|
|
while (mTabs.Count > 0)
|
|
{
|
|
TabButton tab = mTabs[0];
|
|
RemoveTab(tab, false);
|
|
tabbedView.AddTab(tab, tabbedView.GetInsertPositionFromCursor());
|
|
tab.Activate();
|
|
}
|
|
mParentDockingFrame.RemoveDockedWidget(this);
|
|
|
|
Closed();
|
|
BFApp.sApp.DeferDelete(this);
|
|
}
|
|
else
|
|
{
|
|
if (frame.mWidgetWindow != mWidgetWindow)
|
|
mAllowInterpolate = false;
|
|
|
|
if (mWidth != 0)
|
|
SetRequestedSize(mWidth, mHeight);
|
|
frame.StartContentInterpolate();
|
|
if (mParentDockingFrame != null)
|
|
{
|
|
mParentDockingFrame.StartContentInterpolate();
|
|
mParentDockingFrame.RemoveDockedWidget(this);
|
|
}
|
|
frame.AddDockedWidget(this, refWidget, align);
|
|
}
|
|
}
|
|
|
|
public override void Resize(float x, float y, float width, float height)
|
|
{
|
|
base.Resize(x, y, width, height);
|
|
|
|
ResizeTabs(true);
|
|
|
|
TabButton tab = GetActiveTab();
|
|
if (tab != null)
|
|
tab.ResizeContent();
|
|
}
|
|
}
|
|
}
|