Initial release
This commit is contained in:
@@ -0,0 +1,546 @@
|
||||
// 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 "crowcolumnview.h"
|
||||
#include "animation/animations.h"
|
||||
#include "animation/timingfunctions.h"
|
||||
#include <vector>
|
||||
|
||||
namespace VSTGUI {
|
||||
namespace Layouting {
|
||||
//--------------------------------------------------------------------------------
|
||||
using CRects = std::vector<CRect>;
|
||||
|
||||
enum class Alignment
|
||||
{
|
||||
kTopLeft,
|
||||
kTopCenter,
|
||||
kTopRight,
|
||||
kMiddleLeft,
|
||||
kMiddleCenter,
|
||||
kMiddleRight,
|
||||
kBottomLeft,
|
||||
kBottomCenter,
|
||||
kBottomRight
|
||||
};
|
||||
|
||||
enum class Style
|
||||
{
|
||||
kRow,
|
||||
kColumn
|
||||
};
|
||||
|
||||
//--------------------------------------------------------------------------------
|
||||
Alignment translate (const CRowColumnView::LayoutStyle layoutStyle)
|
||||
{
|
||||
switch (layoutStyle)
|
||||
{
|
||||
case CRowColumnView::LayoutStyle::kTopLeft:
|
||||
return Alignment::kTopLeft;
|
||||
case CRowColumnView::LayoutStyle::kTopCenter:
|
||||
return Alignment::kTopCenter;
|
||||
case CRowColumnView::LayoutStyle::kTopRight:
|
||||
return Alignment::kTopRight;
|
||||
case CRowColumnView::LayoutStyle::kMiddleLeft:
|
||||
return Alignment::kMiddleLeft;
|
||||
case CRowColumnView::LayoutStyle::kMiddleCenter:
|
||||
return Alignment::kMiddleCenter;
|
||||
case CRowColumnView::LayoutStyle::kMiddleRight:
|
||||
return Alignment::kMiddleRight;
|
||||
case CRowColumnView::LayoutStyle::kBottomLeft:
|
||||
return Alignment::kBottomLeft;
|
||||
case CRowColumnView::LayoutStyle::kBottomCenter:
|
||||
return Alignment::kBottomCenter;
|
||||
case CRowColumnView::LayoutStyle::kBottomRight:
|
||||
return Alignment::kBottomRight;
|
||||
default:
|
||||
return Alignment::kTopLeft;
|
||||
}
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------
|
||||
Style translate (const CRowColumnView::Style style)
|
||||
{
|
||||
switch (style)
|
||||
{
|
||||
case CRowColumnView::Style::kRowStyle:
|
||||
return Style::kRow;
|
||||
case CRowColumnView::Style::kColumnStyle:
|
||||
return Style::kColumn;
|
||||
default:
|
||||
return Style::kRow;
|
||||
}
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------
|
||||
CPoint computeRectOffset (const CPoint& parent, const CPoint& rect, const Alignment alignment)
|
||||
{
|
||||
CPoint offset (0., 0.);
|
||||
switch (alignment)
|
||||
{
|
||||
case Alignment::kTopLeft:
|
||||
{
|
||||
offset = CPoint (0., 0.);
|
||||
break;
|
||||
}
|
||||
case Alignment::kTopCenter:
|
||||
{
|
||||
offset = CPoint ((parent.x - rect.x) / 2., 0.);
|
||||
break;
|
||||
}
|
||||
case Alignment::kTopRight:
|
||||
{
|
||||
offset = CPoint (parent.x - rect.x, 0.);
|
||||
break;
|
||||
}
|
||||
case Alignment::kMiddleLeft:
|
||||
{
|
||||
offset = CPoint (0., (parent.y - rect.y) / 2.);
|
||||
break;
|
||||
}
|
||||
case Alignment::kMiddleCenter:
|
||||
{
|
||||
offset = CPoint ((parent.x - rect.x) / 2., (parent.y - rect.y) / 2.);
|
||||
break;
|
||||
}
|
||||
case Alignment::kMiddleRight:
|
||||
{
|
||||
offset = CPoint (parent.x - rect.x, (parent.y - rect.y) / 2.);
|
||||
break;
|
||||
}
|
||||
case Alignment::kBottomLeft:
|
||||
{
|
||||
offset = CPoint (0., parent.y - rect.y);
|
||||
break;
|
||||
}
|
||||
case Alignment::kBottomCenter:
|
||||
{
|
||||
offset = CPoint ((parent.x - rect.x) / 2., parent.y - rect.y);
|
||||
break;
|
||||
}
|
||||
case Alignment::kBottomRight:
|
||||
{
|
||||
offset = CPoint (parent.x - rect.x, parent.y - rect.y);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return offset;
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------
|
||||
CRect computeGroupRect (const CRect& parent, const CRects& children, const Alignment alignment,
|
||||
const Style style, double spacing)
|
||||
{
|
||||
CPoint maxSize;
|
||||
if (style == Style::kRow)
|
||||
{
|
||||
for (const auto& rect : children)
|
||||
{
|
||||
if (maxSize.x < rect.getWidth ())
|
||||
maxSize.x = rect.getWidth ();
|
||||
|
||||
maxSize.y = maxSize.y + rect.getHeight () + spacing;
|
||||
}
|
||||
|
||||
// Remove the last spacing again
|
||||
if (!children.empty ())
|
||||
maxSize.y -= spacing;
|
||||
}
|
||||
else
|
||||
{
|
||||
for (const auto& rect : children)
|
||||
{
|
||||
if (maxSize.y < rect.getHeight ())
|
||||
maxSize.y = rect.getHeight ();
|
||||
|
||||
maxSize.x = maxSize.x + rect.getWidth () + spacing;
|
||||
}
|
||||
|
||||
// Remove the last spacing again
|
||||
if (!children.empty ())
|
||||
maxSize.x -= spacing;
|
||||
}
|
||||
|
||||
const auto offset = computeRectOffset (parent.getSize (), maxSize, alignment);
|
||||
return CRect ().setSize (maxSize).offset (offset);
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------
|
||||
CRect computeGroupRect (const CViewContainer& parent, const Alignment alignment, const Style style,
|
||||
const double spacing)
|
||||
{
|
||||
CRects childrenViewSizes;
|
||||
parent.forEachChild (
|
||||
[&] (const CView* child) { childrenViewSizes.push_back (child->getViewSize ()); });
|
||||
|
||||
return computeGroupRect (parent.getViewSize (), childrenViewSizes, alignment, style, spacing);
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------
|
||||
// AutoLayout Declaration
|
||||
/// @brief An auto layout feature for the CRowColumnView
|
||||
///
|
||||
/// The AutoLayout flexibly layouts the children of a CRowColumnView. The children are grouped and
|
||||
/// aligned among themselves depending on whether the style is a row (left to right) or a
|
||||
/// column (top to bottom). The group is moved inside the parent to either of the nine
|
||||
/// positions (top-left, top-center, top-right, middle-left, middle-center, middle-right,
|
||||
/// bottom-left, bottom-center or bottom-right). If the size of the parent is changed, the layout
|
||||
/// and alignment of the group is always retained.
|
||||
//--------------------------------------------------------------------------------
|
||||
class AutoLayout
|
||||
{
|
||||
public:
|
||||
//--------------------------------------------------------------------------------
|
||||
AutoLayout (const CViewContainer& parent, const Alignment alignment, const Style style,
|
||||
const double spacing)
|
||||
: alignment (alignment), style (style)
|
||||
{
|
||||
groupRect = Layouting::computeGroupRect (parent, alignment, style, spacing);
|
||||
}
|
||||
|
||||
/** moves the child rect to the calculated position (inside the group and inside the parent) */
|
||||
auto moveRect (CRect& viewSize) -> CRect&
|
||||
{
|
||||
// Offset the viewSize inside the groupRect...
|
||||
const CPoint offset =
|
||||
Layouting::computeRectOffset (groupRect.getSize (), viewSize.getSize (), alignment);
|
||||
if (style == Style::kRow)
|
||||
viewSize.offset (offset.x, 0.);
|
||||
else
|
||||
viewSize.offset (0., offset.y);
|
||||
|
||||
//...and offset by topLeft of the groupRect afterwards, in order to align with the
|
||||
// 'real' parent.
|
||||
return viewSize.offset (groupRect.getTopLeft ());
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------
|
||||
private:
|
||||
const Alignment alignment = Alignment::kTopLeft;
|
||||
const Style style = Style::kRow;
|
||||
CRect groupRect;
|
||||
};
|
||||
|
||||
//--------------------------------------------------------------------------------
|
||||
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------
|
||||
CRowColumnView::CRowColumnView (const CRect& size, Style style, LayoutStyle layoutStyle,
|
||||
CCoord spacing, const CRect& margin)
|
||||
: CAutoLayoutContainerView (size)
|
||||
, style (style)
|
||||
, layoutStyle (layoutStyle)
|
||||
, spacing (spacing)
|
||||
, margin (margin)
|
||||
, flags (0)
|
||||
, layoutGuard (false)
|
||||
, viewResizeAnimationTime (200)
|
||||
{
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------
|
||||
void CRowColumnView::setStyle (Style newStyle)
|
||||
{
|
||||
if (newStyle != style)
|
||||
{
|
||||
style = newStyle;
|
||||
if (isAttached ())
|
||||
layoutViews ();
|
||||
}
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------
|
||||
void CRowColumnView::setSpacing (CCoord newSpacing)
|
||||
{
|
||||
if (newSpacing != spacing)
|
||||
{
|
||||
spacing = newSpacing;
|
||||
if (isAttached ())
|
||||
layoutViews ();
|
||||
}
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------
|
||||
void CRowColumnView::setMargin (const CRect& newMargin)
|
||||
{
|
||||
if (newMargin != margin)
|
||||
{
|
||||
margin = newMargin;
|
||||
if (isAttached ())
|
||||
layoutViews ();
|
||||
}
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------
|
||||
void CRowColumnView::setLayoutStyle (LayoutStyle inLayoutStyle)
|
||||
{
|
||||
if (inLayoutStyle != layoutStyle)
|
||||
{
|
||||
layoutStyle = inLayoutStyle;
|
||||
if (isAttached ())
|
||||
layoutViews ();
|
||||
}
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------
|
||||
bool CRowColumnView::isAnimateViewResizing () const { return hasBit (flags, kAnimateViewResizing); }
|
||||
|
||||
//--------------------------------------------------------------------------------
|
||||
void CRowColumnView::setAnimateViewResizing (bool state)
|
||||
{
|
||||
setBit (flags, kAnimateViewResizing, state);
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------
|
||||
bool CRowColumnView::hideClippedSubviews () const { return hasBit (flags, kHideClippedSubViews); }
|
||||
|
||||
//--------------------------------------------------------------------------------
|
||||
void CRowColumnView::setHideClippedSubviews (bool state)
|
||||
{
|
||||
setBit (flags, kHideClippedSubViews, state);
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------
|
||||
void CRowColumnView::resizeSubView (CView* view, const CRect& newSize)
|
||||
{
|
||||
if (view->getViewSize () != newSize)
|
||||
{
|
||||
if (isAttached () && isAnimateViewResizing () && viewResizeAnimationTime > 0)
|
||||
{
|
||||
view->setAttribute (ViewAnimatesResizingAttr, true);
|
||||
view->addAnimation (
|
||||
"CRowColumnResizing", new Animation::ViewSizeAnimation (newSize, false),
|
||||
new Animation::LinearTimingFunction (viewResizeAnimationTime),
|
||||
[] (CView* v, auto, auto) { v->removeAttribute (ViewAnimatesResizingAttr); }, true);
|
||||
}
|
||||
else
|
||||
{
|
||||
view->setViewSize (newSize);
|
||||
view->setMouseableArea (newSize);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------
|
||||
void CRowColumnView::getMaxChildViewSize (CPoint& maxSize)
|
||||
{
|
||||
forEachChild ([&] (CView* view) {
|
||||
const CRect& viewSize = view->getViewSize ();
|
||||
if (viewSize.getWidth () > maxSize.x)
|
||||
maxSize.x = viewSize.getWidth ();
|
||||
if (viewSize.getHeight () > maxSize.y)
|
||||
maxSize.y = viewSize.getHeight ();
|
||||
});
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------
|
||||
void CRowColumnView::layoutViewsEqualSize ()
|
||||
{
|
||||
CPoint maxSize;
|
||||
getMaxChildViewSize (maxSize);
|
||||
if (style == kRowStyle)
|
||||
maxSize.x = getViewSize ().getWidth () - (margin.right + margin.left);
|
||||
else
|
||||
maxSize.y = getViewSize ().getHeight () - (margin.top + margin.bottom);
|
||||
|
||||
Layouting::AutoLayout layout (*this, Layouting::translate (layoutStyle),
|
||||
Layouting::translate (style), spacing);
|
||||
|
||||
CPoint location = margin.getTopLeft ();
|
||||
forEachChild ([&] (CView* view) {
|
||||
CRect viewSize = view->getViewSize ();
|
||||
viewSize.originize ();
|
||||
viewSize.offset (location.x, location.y);
|
||||
switch (layoutStyle)
|
||||
{
|
||||
case kStretchEqualy:
|
||||
{
|
||||
viewSize.setWidth (maxSize.x);
|
||||
viewSize.setHeight (maxSize.y);
|
||||
break;
|
||||
}
|
||||
case kCenterEqualy:
|
||||
{
|
||||
CCoord diffX = (maxSize.x - viewSize.getWidth ()) / 2.;
|
||||
CCoord diffY = (maxSize.y - viewSize.getHeight ()) / 2.;
|
||||
viewSize.offset (diffX, diffY);
|
||||
break;
|
||||
}
|
||||
case kRightBottomEqualy:
|
||||
{
|
||||
CCoord diffX = maxSize.x - viewSize.getWidth ();
|
||||
CCoord diffY = maxSize.y - viewSize.getHeight ();
|
||||
viewSize.offset (diffX, diffY);
|
||||
break;
|
||||
}
|
||||
case kTopLeft:
|
||||
case kTopCenter:
|
||||
case kTopRight:
|
||||
case kMiddleLeft:
|
||||
case kMiddleCenter:
|
||||
case kMiddleRight:
|
||||
case kBottomLeft:
|
||||
case kBottomCenter:
|
||||
case kBottomRight:
|
||||
{
|
||||
layout.moveRect (viewSize);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
resizeSubView (view, viewSize);
|
||||
if (style == kRowStyle)
|
||||
{
|
||||
location.y += spacing;
|
||||
location.y += viewSize.getHeight ();
|
||||
}
|
||||
else
|
||||
{
|
||||
location.x += spacing;
|
||||
location.x += viewSize.getWidth ();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------
|
||||
void CRowColumnView::layoutViews ()
|
||||
{
|
||||
if (layoutGuard == false)
|
||||
{
|
||||
layoutGuard = true;
|
||||
layoutViewsEqualSize ();
|
||||
if (hideClippedSubviews ())
|
||||
{
|
||||
forEachChild ([&] (CView* view) {
|
||||
if (view->getVisibleViewSize () != view->getViewSize ())
|
||||
view->setVisible (false);
|
||||
else
|
||||
view->setVisible (true);
|
||||
});
|
||||
}
|
||||
layoutGuard = false;
|
||||
}
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------
|
||||
bool CRowColumnView::sizeToFit ()
|
||||
{
|
||||
if (!getChildren ().empty ())
|
||||
{
|
||||
CRect viewSize = getViewSize ();
|
||||
CPoint maxSize;
|
||||
if (style == kRowStyle)
|
||||
{
|
||||
forEachChild ([&] (CView* view) {
|
||||
const CRect& size = view->getViewSize ();
|
||||
if (size.getWidth () > maxSize.x)
|
||||
maxSize.x = size.getWidth ();
|
||||
maxSize.y += size.getHeight () + spacing;
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
forEachChild ([&] (CView* view) {
|
||||
CRect size = view->getViewSize ();
|
||||
maxSize.x += size.getWidth () + spacing;
|
||||
if (size.bottom > maxSize.y)
|
||||
maxSize.y = size.getHeight ();
|
||||
});
|
||||
}
|
||||
viewSize.setWidth (maxSize.x + margin.left + margin.right);
|
||||
viewSize.setHeight (maxSize.y + margin.top + margin.bottom);
|
||||
if (viewSize != getViewSize ())
|
||||
{
|
||||
invalid ();
|
||||
auto autosizeState = getAutosizingEnabled ();
|
||||
setAutosizingEnabled (false);
|
||||
CViewContainer::setViewSize (viewSize);
|
||||
CViewContainer::setMouseableArea (viewSize);
|
||||
setAutosizingEnabled (autosizeState);
|
||||
invalid ();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------
|
||||
CMessageResult CRowColumnView::notify (CBaseObject* sender, IdStringPtr message)
|
||||
{
|
||||
if (message == kMsgViewSizeChanged)
|
||||
{
|
||||
bool animatesResizing = false;
|
||||
static_cast<CView*> (sender)->getAttribute (ViewAnimatesResizingAttr, animatesResizing);
|
||||
if (isAttached () && !animatesResizing)
|
||||
layoutViews ();
|
||||
}
|
||||
return CViewContainer::notify (sender, message);
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------
|
||||
//--------------------------------------------------------------------------------
|
||||
//--------------------------------------------------------------------------------
|
||||
CAutoLayoutContainerView::CAutoLayoutContainerView (const CRect& size) : CViewContainer (size) {}
|
||||
|
||||
//--------------------------------------------------------------------------------
|
||||
bool CAutoLayoutContainerView::attached (CView* parent)
|
||||
{
|
||||
if (!isAttached ())
|
||||
{
|
||||
layoutViews ();
|
||||
return CViewContainer::attached (parent);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------
|
||||
void CAutoLayoutContainerView::setViewSize (const CRect& rect, bool invalid)
|
||||
{
|
||||
CViewContainer::setViewSize (rect, invalid);
|
||||
if (isAttached ())
|
||||
layoutViews ();
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------
|
||||
bool CAutoLayoutContainerView::addView (CView* pView, CView* pBefore)
|
||||
{
|
||||
if (CViewContainer::addView (pView, pBefore))
|
||||
{
|
||||
if (isAttached ())
|
||||
layoutViews ();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------
|
||||
bool CAutoLayoutContainerView::removeView (CView* pView, bool withForget)
|
||||
{
|
||||
if (CViewContainer::removeView (pView, withForget))
|
||||
{
|
||||
if (isAttached ())
|
||||
layoutViews ();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------
|
||||
bool CAutoLayoutContainerView::changeViewZOrder (CView* view, uint32_t newIndex)
|
||||
{
|
||||
if (CViewContainer::changeViewZOrder (view, newIndex))
|
||||
{
|
||||
if (isAttached ())
|
||||
layoutViews ();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
} // VSTGUI
|
||||
Reference in New Issue
Block a user