554 lines
14 KiB
C++
554 lines
14 KiB
C++
// This file is part of VSTGUI. It is subject to the license terms
|
|
// in the LICENSE file found in the top-level directory of this
|
|
// distribution and at http://github.com/steinbergmedia/vstgui/LICENSE
|
|
|
|
#include "ctabview.h"
|
|
#include "cfont.h"
|
|
#include "cbitmap.h"
|
|
#include "cdrawcontext.h"
|
|
#include "dragging.h"
|
|
#include "controls/cbuttons.h"
|
|
#include "cgraphicspath.h"
|
|
|
|
namespace VSTGUI {
|
|
|
|
#define kTabButtonTagStart 20000
|
|
|
|
/// @cond ignore
|
|
//-----------------------------------------------------------------------------
|
|
class CTabButton : public COnOffButton, public DropTargetAdapter
|
|
//-----------------------------------------------------------------------------
|
|
{
|
|
public:
|
|
CTabButton (const CRect &size, IControlListener *listener, int32_t tag, CBitmap *background, UTF8StringPtr inName)
|
|
: COnOffButton (size, listener, tag, background)
|
|
, name (inName)
|
|
{
|
|
activeTextColor = kBlackCColor;
|
|
inactiveTextColor (90, 90, 90, 255);
|
|
textFont = kSystemFont; textFont->remember ();
|
|
}
|
|
|
|
~CTabButton () noexcept override
|
|
{
|
|
if (textFont)
|
|
textFont->forget ();
|
|
}
|
|
|
|
void draw (CDrawContext *pContext) override
|
|
{
|
|
COnOffButton::draw (pContext);
|
|
if (!name.empty ())
|
|
{
|
|
pContext->setFont (textFont);
|
|
pContext->setFontColor (value > 0.f ? activeTextColor : inactiveTextColor);
|
|
pContext->drawString (name, getViewSize ());
|
|
}
|
|
}
|
|
|
|
CMouseEventResult onMouseDown (CPoint &where, const CButtonState& button) override
|
|
{
|
|
value = ((int32_t)value) ? 0.f : 1.f;
|
|
|
|
valueChanged ();
|
|
|
|
return kMouseDownEventHandledButDontNeedMovedOrUpEvents;
|
|
}
|
|
|
|
SharedPointer<IDropTarget> getDropTarget () override { return this; }
|
|
DragOperation onDragEnter (DragEventData data) override
|
|
{
|
|
if (value == 0.f)
|
|
{
|
|
value = 1.f;
|
|
valueChanged ();
|
|
}
|
|
return DragOperation::None;
|
|
}
|
|
|
|
void setTextFont (CFontRef font) { if (textFont) textFont->forget (); textFont = font; textFont->remember ();}
|
|
void setActiveTextColor (const CColor& color) { activeTextColor = color; }
|
|
void setInactiveTextColor (const CColor& color) { inactiveTextColor = color; }
|
|
|
|
CLASS_METHODS (CTabButton, COnOffButton)
|
|
protected:
|
|
UTF8String name;
|
|
CFontRef textFont;
|
|
CColor activeTextColor;
|
|
CColor inactiveTextColor;
|
|
};
|
|
|
|
//-----------------------------------------------------------------------------
|
|
class CTabChildView : public NonAtomicReferenceCounted
|
|
//-----------------------------------------------------------------------------
|
|
{
|
|
public:
|
|
explicit CTabChildView (CView* view)
|
|
: view (view)
|
|
, previous (nullptr)
|
|
, next (nullptr)
|
|
, button (nullptr)
|
|
{
|
|
}
|
|
|
|
~CTabChildView () noexcept override
|
|
{
|
|
view->forget ();
|
|
}
|
|
|
|
CView* view;
|
|
CTabChildView* previous;
|
|
CTabChildView* next;
|
|
CControl* button;
|
|
};
|
|
/// @endcond
|
|
|
|
//-----------------------------------------------------------------------------
|
|
CTabView::CTabView (const CRect& size, CBitmap* tabBitmap, CBitmap* background, TabPosition tabPosition, int32_t style)
|
|
: CViewContainer (size)
|
|
, numberOfChilds (0)
|
|
, tabPosition (tabPosition)
|
|
, style (style)
|
|
, tabSize (CRect (0, 0, 0, 0))
|
|
, tabBitmap (tabBitmap)
|
|
, firstChild (nullptr)
|
|
, lastChild (nullptr)
|
|
, currentChild (nullptr)
|
|
{
|
|
setBackground (background);
|
|
if (tabBitmap)
|
|
{
|
|
tabBitmap->remember ();
|
|
tabSize.right = tabBitmap->getWidth ();
|
|
tabSize.bottom = tabBitmap->getHeight ();
|
|
}
|
|
setTransparency (true);
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
CTabView::CTabView (const CRect& size, const CRect& tabSize, CBitmap* background, TabPosition tabPosition, int32_t style)
|
|
: CViewContainer (size)
|
|
, numberOfChilds (0)
|
|
, currentTab (-1)
|
|
, tabPosition (tabPosition)
|
|
, style (style)
|
|
, tabSize (tabSize)
|
|
, tabBitmap (nullptr)
|
|
, firstChild (nullptr)
|
|
, lastChild (nullptr)
|
|
, currentChild (nullptr)
|
|
{
|
|
setBackground (background);
|
|
setTransparency (true);
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
CTabView::~CTabView () noexcept
|
|
{
|
|
setParentView (nullptr);
|
|
setParentFrame (nullptr);
|
|
removeAllTabs ();
|
|
if (tabBitmap)
|
|
tabBitmap->forget ();
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
void CTabView::setAutosizeFlags (int32_t flags)
|
|
{
|
|
CViewContainer::setAutosizeFlags (flags);
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
bool CTabView::addTab (CView* view, UTF8StringPtr name, CBitmap* inTabBitmap)
|
|
{
|
|
if (!view)
|
|
return false;
|
|
if (inTabBitmap == nullptr)
|
|
inTabBitmap = tabBitmap;
|
|
|
|
auto* b = new CTabButton (CRect (0, 0, 0, 0), nullptr, 0, inTabBitmap, name);
|
|
b->setTransparency (true);
|
|
|
|
return addTab (view, b);
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
bool CTabView::addTab (CView* view, CControl* button)
|
|
{
|
|
if (!view || !button)
|
|
return false;
|
|
|
|
CViewContainer* tabContainer = hasChildren () ? getView (0)->asViewContainer () : nullptr;
|
|
if (tabContainer == nullptr)
|
|
{
|
|
int32_t asf = kAutosizeLeft | kAutosizeTop | kAutosizeRight | kAutosizeColumn;
|
|
CRect tsc (0, 0, getViewSize ().getWidth (), tabSize.getHeight () / 2);
|
|
switch (tabPosition)
|
|
{
|
|
case kPositionBottom:
|
|
{
|
|
asf = kAutosizeLeft | kAutosizeBottom | kAutosizeRight | kAutosizeColumn;
|
|
tsc.offset (0, getViewSize ().getHeight () - tabSize.getHeight () / 2);
|
|
break;
|
|
}
|
|
case kPositionLeft:
|
|
{
|
|
asf = kAutosizeLeft | kAutosizeTop | kAutosizeBottom | kAutosizeRow;
|
|
tsc.setWidth (tabSize.getWidth ());
|
|
tsc.setHeight (getViewSize ().getHeight ());
|
|
break;
|
|
}
|
|
case kPositionRight:
|
|
{
|
|
asf = kAutosizeRight | kAutosizeTop | kAutosizeBottom | kAutosizeRow;
|
|
tsc.setWidth (getViewSize ().getWidth ());
|
|
tsc.left = tsc.right - tabSize.getWidth ();
|
|
tsc.setHeight (getViewSize ().getHeight ());
|
|
break;
|
|
}
|
|
case kPositionTop:
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
tabContainer = new CViewContainer (tsc);
|
|
tabContainer->setTransparency (true);
|
|
tabContainer->setAutosizeFlags (asf);
|
|
addView (tabContainer);
|
|
}
|
|
CRect ts (tabSize.left, tabSize.top, tabSize.getWidth (), tabSize.getHeight () / 2);
|
|
switch (tabPosition)
|
|
{
|
|
case kPositionTop:
|
|
ts.offset (tabSize.getWidth () * numberOfChilds, 0); break;
|
|
case kPositionBottom:
|
|
ts.offset (tabSize.getWidth () * numberOfChilds, 0); break;
|
|
case kPositionLeft:
|
|
ts.offset (0, tabSize.getHeight () / 2 * numberOfChilds); break;
|
|
case kPositionRight:
|
|
ts.offset (0, tabSize.getHeight () / 2 * numberOfChilds); break;
|
|
}
|
|
button->setViewSize (ts, false);
|
|
button->setMouseableArea (ts);
|
|
button->setListener (this);
|
|
button->setTag (numberOfChilds + kTabButtonTagStart);
|
|
tabContainer->addView (button);
|
|
|
|
CRect tabViewSize;
|
|
getTabViewSize (tabViewSize);
|
|
view->setViewSize (tabViewSize);
|
|
view->setMouseableArea (tabViewSize);
|
|
|
|
auto* v = new CTabChildView (view);
|
|
v->button = button;
|
|
if (lastChild)
|
|
{
|
|
lastChild->next = v;
|
|
v->previous = lastChild;
|
|
lastChild = v;
|
|
}
|
|
else
|
|
{
|
|
firstChild = lastChild = v;
|
|
setCurrentChild (v);
|
|
}
|
|
numberOfChilds++;
|
|
return true;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
bool CTabView::removeTab (CView* view)
|
|
{
|
|
if (!view)
|
|
return false;
|
|
|
|
CViewContainer* tabContainer = hasChildren () ? getView (0)->asViewContainer () : nullptr;
|
|
if (!tabContainer)
|
|
return false;
|
|
CTabChildView* v = firstChild;
|
|
while (v)
|
|
{
|
|
if (v->view == view)
|
|
{
|
|
if (v->previous)
|
|
v->previous->next = v->next;
|
|
if (v->next)
|
|
v->next->previous = v->previous;
|
|
if (v == currentChild)
|
|
{
|
|
setCurrentChild (v->previous ? v->previous : v->next);
|
|
if (v->previous == nullptr && v->next == nullptr)
|
|
currentTab = -1;
|
|
}
|
|
tabContainer->removeView (v->button, true);
|
|
v->forget ();
|
|
numberOfChilds--;
|
|
return true;
|
|
}
|
|
v = v->next;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
bool CTabView::removeAllTabs ()
|
|
{
|
|
setCurrentChild (nullptr);
|
|
CTabChildView* v = lastChild;
|
|
while (v)
|
|
{
|
|
CTabChildView* next = v->previous;
|
|
removeTab (v->view);
|
|
v = next;
|
|
}
|
|
firstChild = nullptr;
|
|
lastChild = nullptr;
|
|
numberOfChilds = 0;
|
|
currentTab = -1;
|
|
return true;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
bool CTabView::selectTab (int32_t index)
|
|
{
|
|
if (index > numberOfChilds)
|
|
return false;
|
|
CTabChildView* v = firstChild;
|
|
int32_t i = 0;
|
|
while (v)
|
|
{
|
|
if (index == i)
|
|
break;
|
|
v = v->next;
|
|
i++;
|
|
}
|
|
if (v)
|
|
{
|
|
setCurrentChild (v);
|
|
currentTab = i;
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
void CTabView::setCurrentChild (CTabChildView* childView)
|
|
{
|
|
if (currentChild)
|
|
{
|
|
if (childView == currentChild)
|
|
{
|
|
if (currentChild->button)
|
|
currentChild->button->setValue (1.f);
|
|
return;
|
|
}
|
|
if (currentChild->button)
|
|
currentChild->button->setValue (0.f);
|
|
removeView (currentChild->view, false);
|
|
}
|
|
currentChild = childView;
|
|
if (currentChild)
|
|
{
|
|
addView (currentChild->view);
|
|
if (currentChild->button)
|
|
currentChild->button->setValue (1.f);
|
|
}
|
|
invalid ();
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
void CTabView::drawBackgroundRect (CDrawContext *pContext, const CRect& _updateRect)
|
|
{
|
|
CRect oldClip = pContext->getClipRect (oldClip);
|
|
CRect updateRect (_updateRect);
|
|
CViewContainer* tabContainer = hasChildren () ? getView (0)->asViewContainer () : nullptr;
|
|
if (tabContainer)
|
|
{
|
|
CRect tcRect = tabContainer->getViewSize ();
|
|
if (updateRect.top < tcRect.bottom)
|
|
updateRect.top = tcRect.bottom;
|
|
}
|
|
pContext->setClipRect (updateRect);
|
|
CViewContainer::drawBackgroundRect (pContext, updateRect);
|
|
pContext->setClipRect (oldClip);
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
void CTabView::valueChanged (CControl *pControl)
|
|
{
|
|
selectTab (pControl->getTag () - kTabButtonTagStart);
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
void CTabView::setTabViewInsets (const CPoint& inset)
|
|
{
|
|
tabViewInset = inset;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
CRect& CTabView::getTabViewSize (CRect& rect) const
|
|
{
|
|
rect = getViewSize ();
|
|
rect.originize ();
|
|
switch (tabPosition)
|
|
{
|
|
case kPositionTop:
|
|
rect.top += tabSize.getHeight () / 2; break;
|
|
case kPositionBottom:
|
|
rect.bottom -= tabSize.getHeight () / 2; break;
|
|
case kPositionLeft:
|
|
rect.left += tabSize.getWidth (); break;
|
|
case kPositionRight:
|
|
rect.right -= tabSize.getWidth (); break;
|
|
}
|
|
rect.inset (tabViewInset.x, tabViewInset.y);
|
|
return rect;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
void CTabView::setTabFontStyle (const CFontRef font, CCoord fontSize, CColor selectedColor, CColor deselectedColor)
|
|
{
|
|
auto tabFont = makeOwned<CFontDesc> (*font);
|
|
tabFont->setSize (fontSize);
|
|
CTabChildView* v = firstChild;
|
|
while (v)
|
|
{
|
|
if (auto* button = dynamic_cast<CTabButton*>(v->button))
|
|
{
|
|
button->setTextFont (tabFont);
|
|
button->setActiveTextColor (selectedColor);
|
|
button->setInactiveTextColor (deselectedColor);
|
|
}
|
|
v = v->next;
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
void CTabView::alignTabs (TabAlignment alignment)
|
|
{
|
|
CCoord allTabsWidth;
|
|
CCoord viewWidth;
|
|
CCoord offset = 0;
|
|
CRect ts (tabSize.left, tabSize.top, tabSize.getWidth (), tabSize.getHeight () / 2);
|
|
if (tabPosition == kPositionTop || tabPosition == kPositionBottom)
|
|
{
|
|
allTabsWidth = tabSize.getWidth () * numberOfChilds;
|
|
viewWidth = getViewSize ().getWidth ();
|
|
}
|
|
else
|
|
{
|
|
allTabsWidth = (tabSize.getHeight () / 2) * numberOfChilds;
|
|
viewWidth = getViewSize ().getHeight ();
|
|
}
|
|
if (alignment == kAlignCenter)
|
|
offset = (viewWidth - allTabsWidth) / 2;
|
|
else if (alignment == kAlignLeft)
|
|
offset = 0;
|
|
else if (alignment == kAlignRight)
|
|
offset = viewWidth - allTabsWidth;
|
|
if (tabPosition == kPositionTop)
|
|
ts.offset (offset, 0);
|
|
else if (tabPosition == kPositionBottom)
|
|
ts.offset (offset, 0);
|
|
else if (tabPosition == kPositionLeft)
|
|
ts.offset (0, offset);
|
|
else if (tabPosition == kPositionRight)
|
|
ts.offset (0, offset);
|
|
CTabChildView* v = firstChild;
|
|
while (v)
|
|
{
|
|
v->button->setViewSize (ts);
|
|
v->button->setMouseableArea (ts);
|
|
if (tabPosition == kPositionTop || tabPosition == kPositionBottom)
|
|
ts.offset (tabSize.getWidth (), 0);
|
|
else
|
|
ts.offset (0, tabSize.getHeight () / 2);
|
|
v = v->next;
|
|
}
|
|
setDirty (true);
|
|
invalid ();
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
void CTabView::setViewSize (const CRect &rect, bool invalid)
|
|
{
|
|
if (rect == getViewSize ())
|
|
return;
|
|
|
|
CRect oldSize (getViewSize ());
|
|
|
|
CCoord widthDelta = rect.getWidth () - oldSize.getWidth ();
|
|
CCoord heightDelta = rect.getHeight () - oldSize.getHeight ();
|
|
|
|
if (widthDelta != 0 || heightDelta != 0)
|
|
{
|
|
uint32_t numSubviews = getNbViews();
|
|
int32_t counter = 1;
|
|
bool treatAsColumn = (getAutosizeFlags () & kAutosizeColumn) ? true : false;
|
|
bool treatAsRow = (getAutosizeFlags () & kAutosizeRow) ? true : false;
|
|
CTabChildView* v = firstChild;
|
|
while (v)
|
|
{
|
|
if (v != currentChild)
|
|
{
|
|
CView* pV = v->view;
|
|
int32_t autosize = pV->getAutosizeFlags ();
|
|
CRect viewSize (pV->getViewSize ());
|
|
CRect mouseSize (pV->getMouseableArea ());
|
|
if (treatAsColumn)
|
|
{
|
|
if (counter)
|
|
{
|
|
viewSize.offset (counter * (widthDelta / (numSubviews)), 0);
|
|
mouseSize.offset (counter * (widthDelta / (numSubviews)), 0);
|
|
}
|
|
viewSize.setWidth (viewSize.getWidth () + (widthDelta / (numSubviews)));
|
|
mouseSize.setWidth (mouseSize.getWidth () + (widthDelta / (numSubviews)));
|
|
}
|
|
else if (widthDelta != 0 && autosize & kAutosizeRight)
|
|
{
|
|
viewSize.right += widthDelta;
|
|
mouseSize.right += widthDelta;
|
|
if (!(autosize & kAutosizeLeft))
|
|
{
|
|
viewSize.left += widthDelta;
|
|
mouseSize.left += widthDelta;
|
|
}
|
|
}
|
|
if (treatAsRow)
|
|
{
|
|
if (counter)
|
|
{
|
|
viewSize.offset (0, counter * (heightDelta / (numSubviews)));
|
|
mouseSize.offset (0, counter * (heightDelta / (numSubviews)));
|
|
}
|
|
viewSize.setHeight (viewSize.getHeight () + (heightDelta / (numSubviews)));
|
|
mouseSize.setHeight (mouseSize.getHeight () + (heightDelta / (numSubviews)));
|
|
}
|
|
else if (heightDelta != 0 && autosize & kAutosizeBottom)
|
|
{
|
|
viewSize.bottom += heightDelta;
|
|
mouseSize.bottom += heightDelta;
|
|
if (!(autosize & kAutosizeTop))
|
|
{
|
|
viewSize.top += heightDelta;
|
|
mouseSize.top += heightDelta;
|
|
}
|
|
}
|
|
if (viewSize != pV->getViewSize ())
|
|
{
|
|
pV->setViewSize (viewSize);
|
|
pV->setMouseableArea (mouseSize);
|
|
}
|
|
}
|
|
v = v->next;
|
|
// counter++;
|
|
}
|
|
}
|
|
|
|
CViewContainer::setViewSize (rect, invalid);
|
|
}
|
|
|
|
|
|
} // VSTGUI
|