975 lines
29 KiB
C++
975 lines
29 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 "cscrollview.h"
|
|
#include "cvstguitimer.h"
|
|
#include "cdrawcontext.h"
|
|
#include "cframe.h"
|
|
#include "dragging.h"
|
|
#include "controls/cscrollbar.h"
|
|
#include "events.h"
|
|
#include "algorithm.h"
|
|
#include "viewlayouter/gridlayouter.h"
|
|
#include <cmath>
|
|
|
|
/// @cond ignore
|
|
namespace VSTGUI {
|
|
|
|
// CScrollContainer is private
|
|
//-----------------------------------------------------------------------------
|
|
class CScrollContainer : public CViewContainer
|
|
//-----------------------------------------------------------------------------
|
|
{
|
|
public:
|
|
CScrollContainer (const CRect &size, const CRect &containerSize);
|
|
CScrollContainer (const CScrollContainer& v);
|
|
~CScrollContainer () override = default;
|
|
|
|
void setScrollOffset (CPoint offset, bool withRedraw = false);
|
|
void getScrollOffset (CPoint& off) const { off = offset; }
|
|
const CPoint& getScrollOffset () const { return offset; }
|
|
|
|
CRect getContainerSize () const { return containerSize; }
|
|
void setContainerSize (const CRect& cs);
|
|
|
|
bool isDirty () const override;
|
|
|
|
void setAutoDragScroll (bool state) { autoDragScroll = state; }
|
|
|
|
bool attached (CView* parent) override;
|
|
CMessageResult notify (CBaseObject* sender, IdStringPtr message) override;
|
|
|
|
SharedPointer<IDropTarget> getDropTarget () override;
|
|
void onDragMove (CPoint where);
|
|
|
|
CLASS_METHODS(CScrollContainer, CViewContainer)
|
|
//-----------------------------------------------------------------------------
|
|
protected:
|
|
struct DropTarget : public IDropTarget, public NonAtomicReferenceCounted
|
|
{
|
|
DropTarget (CScrollContainer* scrollContainer, SharedPointer<IDropTarget>&& parent)
|
|
: scrollContainer (scrollContainer), parent (std::move (parent))
|
|
{
|
|
}
|
|
|
|
DragOperation onDragEnter (DragEventData data) override
|
|
{
|
|
return parent->onDragEnter (data);
|
|
}
|
|
DragOperation onDragMove (DragEventData data) override
|
|
{
|
|
scrollContainer->onDragMove (data.pos);
|
|
return parent->onDragMove (data);
|
|
}
|
|
void onDragLeave (DragEventData data) override
|
|
{
|
|
return parent->onDragLeave (data);
|
|
}
|
|
bool onDrop (DragEventData data) override
|
|
{
|
|
return parent->onDrop (data);
|
|
}
|
|
|
|
CScrollContainer* scrollContainer;
|
|
SharedPointer<IDropTarget> parent;
|
|
};
|
|
|
|
bool getScrollValue (const CPoint& where, float& x, float& y);
|
|
|
|
CRect containerSize;
|
|
CPoint offset;
|
|
bool autoDragScroll;
|
|
bool inScrolling;
|
|
};
|
|
|
|
//-----------------------------------------------------------------------------
|
|
//-----------------------------------------------------------------------------
|
|
//-----------------------------------------------------------------------------
|
|
CScrollContainer::CScrollContainer (const CRect &size, const CRect &containerSize)
|
|
: CViewContainer (size)
|
|
, containerSize (containerSize)
|
|
, offset (CPoint (0, 0))
|
|
, autoDragScroll (false)
|
|
, inScrolling (false)
|
|
{
|
|
setTransparency (true);
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
CScrollContainer::CScrollContainer (const CScrollContainer& v)
|
|
: CViewContainer (v)
|
|
, containerSize (v.containerSize)
|
|
, offset (v.offset)
|
|
, autoDragScroll (v.autoDragScroll)
|
|
, inScrolling (false)
|
|
{
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
void CScrollContainer::setContainerSize (const CRect& cs)
|
|
{
|
|
containerSize = cs;
|
|
setScrollOffset (offset, false);
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
void CScrollContainer::setScrollOffset (CPoint newOffset, bool redraw)
|
|
{
|
|
newOffset.x = floor (newOffset.x + 0.5);
|
|
newOffset.y = floor (newOffset.y + 0.5);
|
|
if (containerSize.getWidth () >= getViewSize ().getWidth ())
|
|
{
|
|
if (newOffset.x < containerSize.left - (containerSize.getWidth () - getViewSize ().getWidth ()))
|
|
newOffset.x = containerSize.left - (containerSize.getWidth () - getViewSize ().getWidth ());
|
|
}
|
|
else
|
|
{
|
|
if (newOffset.x < containerSize.left - containerSize.getWidth ())
|
|
newOffset.x = containerSize.left - containerSize.getWidth ();
|
|
}
|
|
if (newOffset.x > containerSize.right)
|
|
newOffset.x = containerSize.right;
|
|
if (newOffset.y < containerSize.top)
|
|
newOffset.y = containerSize.top;
|
|
if (newOffset.y > containerSize.bottom)
|
|
newOffset.y = containerSize.bottom;
|
|
CPoint diff ((int32_t)(newOffset.x - offset.x), (int32_t)(offset.y - newOffset.y));
|
|
if (diff.x == 0 && diff.y == 0)
|
|
return;
|
|
offset = newOffset;
|
|
inScrolling = true;
|
|
for (const auto& pV : getChildren ())
|
|
{
|
|
CRect r = pV->getViewSize ();
|
|
CRect mr = pV->getMouseableArea ();
|
|
r.offset (diff.x , diff.y);
|
|
pV->setViewSize (r, false);
|
|
mr.offset (diff.x , diff.y);
|
|
pV->setMouseableArea (mr);
|
|
}
|
|
inScrolling = false;
|
|
if (!isAttached ())
|
|
return;
|
|
|
|
if (getTransparency ())
|
|
{
|
|
invalid ();
|
|
}
|
|
else
|
|
{
|
|
CRect scrollRect (0, 0, getViewSize ().getWidth (), getViewSize ().getHeight ());
|
|
CPoint p;
|
|
localToFrame (p);
|
|
scrollRect.offset (p.x, p.y);
|
|
CRect visibleRect = getVisibleSize (CRect (0, 0, getViewSize ().getWidth (), getViewSize ().getHeight ()));
|
|
visibleRect.offset (p.x, p.y);
|
|
scrollRect.bound (visibleRect);
|
|
|
|
CPoint distance (diff.x, diff.y);
|
|
if (distance.x > 0)
|
|
scrollRect.right -= distance.x;
|
|
else if (distance.x < 0)
|
|
scrollRect.left -= distance.x;
|
|
if (distance.y > 0)
|
|
scrollRect.bottom -= distance.y;
|
|
else if (distance.y < 0)
|
|
scrollRect.top -= distance.y;
|
|
getFrame ()->scrollRect (scrollRect, distance);
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
bool CScrollContainer::isDirty () const
|
|
{
|
|
if (CView::isDirty ())
|
|
return true;
|
|
|
|
for (const auto& pV : getChildren ())
|
|
{
|
|
if (pV->isDirty () && pV->isVisible ())
|
|
{
|
|
CRect r = pV->getVisibleViewSize ();
|
|
if (r.getWidth () > 0 && r.getHeight () > 0)
|
|
return true;
|
|
else
|
|
pV->setDirty (false);
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
bool CScrollContainer::getScrollValue (const CPoint& where, float& x, float& y)
|
|
{
|
|
const CCoord kWidth = 10;
|
|
x = 0.f;
|
|
y = 0.f;
|
|
if (where.x <= getViewSize ().left + kWidth)
|
|
{
|
|
x = (float)(where.x - (getViewSize ().left + kWidth));
|
|
}
|
|
else if (where.x >= getViewSize ().right - kWidth)
|
|
{
|
|
x = (float)(where.x - (getViewSize ().right - kWidth));
|
|
}
|
|
if (where.y <= getViewSize ().top + kWidth)
|
|
{
|
|
y = (float)(where.y - (getViewSize ().top + kWidth));
|
|
}
|
|
else if (where.y >= getViewSize ().bottom - kWidth)
|
|
{
|
|
y = (float)(where.y - (getViewSize ().bottom - kWidth));
|
|
}
|
|
return (x != 0.f || y != 0.f);
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
SharedPointer<IDropTarget> CScrollContainer::getDropTarget ()
|
|
{
|
|
auto dropTarget = CViewContainer::getDropTarget ();
|
|
if (autoDragScroll)
|
|
{
|
|
return makeOwned<DropTarget> (this, std::move (dropTarget));
|
|
}
|
|
return dropTarget;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
void CScrollContainer::onDragMove (CPoint where)
|
|
{
|
|
float x, y;
|
|
if (getScrollValue (where, x, y))
|
|
{
|
|
if (auto* scrollView = static_cast<CScrollView*> (getParentView ()))
|
|
{
|
|
CRect r (getViewSize ());
|
|
r.originize ();
|
|
r.offset (x, y);
|
|
scrollView->makeRectVisible (r);
|
|
}
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
bool CScrollContainer::attached (CView* parent)
|
|
{
|
|
bool result = CViewContainer::attached (parent);
|
|
if (getNbViews () == 1)
|
|
{
|
|
if (CView* view = getView (0))
|
|
{
|
|
const CRect& r (view->getViewSize ());
|
|
CRect newContainerSize (containerSize);
|
|
newContainerSize.setWidth (r.getWidth ());
|
|
newContainerSize.setHeight (r.getHeight ());
|
|
if (newContainerSize != containerSize)
|
|
{
|
|
auto* scrollView = static_cast<CScrollView*> (getParentView ());
|
|
if (scrollView)
|
|
scrollView->setContainerSize (newContainerSize);
|
|
}
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
CMessageResult CScrollContainer::notify (CBaseObject* sender, IdStringPtr message)
|
|
{
|
|
if (message == kMsgViewSizeChanged && !inScrolling)
|
|
{
|
|
uint32_t numSubViews = getNbViews ();
|
|
auto* view = static_cast<CView*> (sender);
|
|
if (numSubViews == 1 && view && isChild (view))
|
|
{
|
|
const CRect& r (view->getViewSize ());
|
|
CRect newContainerSize (containerSize);
|
|
newContainerSize.setWidth (r.getWidth ());
|
|
newContainerSize.setHeight (r.getHeight ());
|
|
if (newContainerSize != containerSize)
|
|
{
|
|
auto* scrollView = static_cast<CScrollView*> (getParentView ());
|
|
if (scrollView)
|
|
scrollView->setContainerSize (newContainerSize);
|
|
}
|
|
}
|
|
}
|
|
return getParentView () ? getParentView ()->notify (sender, message) : kMessageUnknown;
|
|
}
|
|
|
|
//------------------------------------------------------------------------
|
|
struct CScrollView::Impl
|
|
{
|
|
CScrollContainer* scrollContainer {nullptr};
|
|
CScrollbar* vScrollbar {nullptr};
|
|
CScrollbar* hScrollbar {nullptr};
|
|
CView* edgeViewTop {nullptr};
|
|
CView* edgeViewLeft {nullptr};
|
|
|
|
CRect containerSize {};
|
|
CCoord scrollbarWidth {};
|
|
int32_t style {};
|
|
int32_t activeScrollbarStyle {};
|
|
|
|
SharedPointer<GridLayouter> layouter;
|
|
|
|
struct Layouter : GridLayouter
|
|
{
|
|
using GridLayouter::GridLayouter;
|
|
|
|
std::optional<ViewLayout> calculateLayout (const CViewContainer& view,
|
|
const Children& children, const CRect& newSize)
|
|
{
|
|
auto& scrollView = static_cast<const CScrollView&> (view);
|
|
scrollView.preLayouting ();
|
|
return GridLayouter::calculateLayout (view, children, newSize);
|
|
}
|
|
};
|
|
};
|
|
|
|
/// @endcond
|
|
|
|
//-----------------------------------------------------------------------------
|
|
//-----------------------------------------------------------------------------
|
|
//-----------------------------------------------------------------------------
|
|
CScrollView::CScrollView (const CRect& size, const CRect& containerSize, int32_t style,
|
|
CCoord scrollbarWidth, CBitmap* pBackground)
|
|
: CViewContainer (size)
|
|
{
|
|
impl = std::make_unique<Impl> ();
|
|
impl->containerSize = containerSize;
|
|
impl->scrollbarWidth = scrollbarWidth;
|
|
impl->style = style;
|
|
|
|
GridLayoutProperties props;
|
|
props.rows = 5;
|
|
props.columns = 5;
|
|
props.rowGap = 0;
|
|
props.columnGap = 0;
|
|
props.alignItems = GridLayoutProperties::AlignItems::Stretch;
|
|
props.justifyItems = GridLayoutProperties::JustifyItems::Stretch;
|
|
props.alignContent = GridLayoutProperties::AlignContent::Center;
|
|
props.justifyContent = GridLayoutProperties::JustifyContent::Center;
|
|
props.autoRows.reserve (5);
|
|
props.autoRows.push_back (CCoord {1}); // border
|
|
props.autoRows.push_back (CCoord {100}); // top-edge view
|
|
props.autoRows.push_back (GridLayoutProperties::Auto {}); // scroll container
|
|
props.autoRows.push_back (CCoord {25}); // horizontal scrollbar
|
|
props.autoRows.push_back (CCoord {1}); // border
|
|
props.autoColumns.reserve (5);
|
|
props.autoColumns.push_back (CCoord {1}); // border
|
|
props.autoColumns.push_back (CCoord {100}); // left-edge view
|
|
props.autoColumns.push_back (GridLayoutProperties::Auto {}); // scroll container
|
|
props.autoColumns.push_back (CCoord {25}); // vertical scrollbar
|
|
props.autoColumns.push_back (CCoord {1}); // border
|
|
props.gridAreas.reserve (5);
|
|
props.gridAreas.push_back ({2, 2, 1, 1}); // scroll container
|
|
props.gridAreas.push_back ({1, 1, 1, 3}); // top-edge view
|
|
props.gridAreas.push_back ({2, 1, 1, 1}); // left-edge view
|
|
props.gridAreas.push_back ({2, 3, 1, 1}); // vertical scrollbar
|
|
props.gridAreas.push_back ({3, 1, 1, 2}); // horizontal scrollbar
|
|
|
|
impl->layouter = makeOwned<Impl::Layouter> (props);
|
|
setViewLayouter (impl->layouter);
|
|
|
|
impl->vScrollbar = new CScrollbar ({}, this, kVSBTag, CScrollbar::kVertical, {});
|
|
impl->hScrollbar = new CScrollbar ({}, this, kHSBTag, CScrollbar::kHorizontal, {});
|
|
impl->edgeViewTop = new CView ({});
|
|
impl->edgeViewLeft = new CView ({});
|
|
impl->scrollContainer = new CScrollContainer ({}, impl->containerSize);
|
|
|
|
CViewContainer::addView (impl->scrollContainer);
|
|
CViewContainer::addView (impl->edgeViewTop);
|
|
CViewContainer::addView (impl->edgeViewLeft);
|
|
CViewContainer::addView (impl->vScrollbar);
|
|
CViewContainer::addView (impl->hScrollbar);
|
|
|
|
impl->scrollContainer->registerViewListener (this);
|
|
|
|
if (pBackground)
|
|
setBackground(pBackground);
|
|
recalculateLayout ();
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
CScrollView::~CScrollView () noexcept = default;
|
|
|
|
//-----------------------------------------------------------------------------
|
|
CRect CScrollView::calculateOptimalContainerSize () const
|
|
{
|
|
auto size = getViewSize ();
|
|
size.originize ();
|
|
if (!(impl->style & kDontDrawFrame))
|
|
size.inset (1, 1);
|
|
if (impl->edgeViewTop)
|
|
size.top += impl->edgeViewTop->getHeight ();
|
|
if (impl->edgeViewLeft)
|
|
size.left += impl->edgeViewLeft->getWidth ();
|
|
if (!(impl->style & kAutoHideScrollbars) && !(impl->style & kOverlayScrollbars))
|
|
{
|
|
if (impl->style & kHorizontalScrollbar)
|
|
size.bottom -= impl->scrollbarWidth;
|
|
if (impl->style & kVerticalScrollbar)
|
|
size.right -= impl->scrollbarWidth;
|
|
}
|
|
size.originize ();
|
|
return size;
|
|
}
|
|
|
|
//------------------------------------------------------------------------
|
|
CRect CScrollView::getVisibleClientRect () const
|
|
{
|
|
if (!impl->scrollContainer)
|
|
return {};
|
|
return impl->scrollContainer->getViewSize ();
|
|
}
|
|
|
|
//------------------------------------------------------------------------
|
|
void CScrollView::preLayouting () const
|
|
{
|
|
auto style = impl->style;
|
|
auto& gridProps = impl->layouter->getProperties ();
|
|
// border
|
|
auto borderWidth = (style & kDontDrawFrame) ? 0. : 1.;
|
|
gridProps.autoRows[0] = borderWidth;
|
|
gridProps.autoRows[4] = borderWidth;
|
|
gridProps.autoColumns[0] = borderWidth;
|
|
gridProps.autoColumns[4] = borderWidth;
|
|
// top-edge view
|
|
gridProps.autoRows[1] = impl->edgeViewTop->getHeight ();
|
|
// left-edge view
|
|
gridProps.autoColumns[1] = impl->edgeViewLeft->getWidth ();
|
|
|
|
if (style & kOverlayScrollbars)
|
|
{
|
|
gridProps.gridAreas[0] = {2, 2, 2, 2}; // scroll container
|
|
impl->hScrollbar->setOverlayStyle (true);
|
|
impl->vScrollbar->setOverlayStyle (true);
|
|
}
|
|
else
|
|
{
|
|
gridProps.gridAreas[0] = {2, 2, 1, 1}; // scroll container
|
|
impl->hScrollbar->setOverlayStyle (false);
|
|
impl->vScrollbar->setOverlayStyle (false);
|
|
}
|
|
|
|
if (style & kAutoHideScrollbars)
|
|
{
|
|
impl->activeScrollbarStyle = 0;
|
|
gridProps.autoRows[3] = 0.;
|
|
gridProps.autoColumns[3] = 0.;
|
|
auto layout =
|
|
impl->layouter->GridLayouter::calculateLayout (*this, getChildren (), getViewSize ());
|
|
if (!layout)
|
|
{
|
|
vstgui_assert (false, "unexpected");
|
|
return;
|
|
}
|
|
auto layoutData = std::any_cast<GridLayouter::LayoutData> (&layout->data);
|
|
auto contSize = layoutData->at (0).first;
|
|
if (style & kHorizontalScrollbar)
|
|
{
|
|
if (impl->containerSize.getWidth () > contSize.getWidth ())
|
|
{
|
|
gridProps.autoRows[3] = impl->scrollbarWidth;
|
|
impl->activeScrollbarStyle |= kHorizontalScrollbar;
|
|
contSize.bottom -= impl->scrollbarWidth;
|
|
}
|
|
if (style & kVerticalScrollbar)
|
|
{
|
|
if (impl->containerSize.getHeight () > contSize.getHeight ())
|
|
{
|
|
gridProps.autoColumns[3] = impl->scrollbarWidth;
|
|
impl->activeScrollbarStyle |= kVerticalScrollbar;
|
|
contSize.right -= impl->scrollbarWidth;
|
|
if (!(impl->activeScrollbarStyle & kHorizontalScrollbar))
|
|
{
|
|
if (impl->containerSize.getWidth () > contSize.getWidth ())
|
|
{
|
|
gridProps.autoRows[3] = impl->scrollbarWidth;
|
|
impl->activeScrollbarStyle |= kHorizontalScrollbar;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else if (style & kVerticalScrollbar)
|
|
{
|
|
if (impl->containerSize.getHeight () > contSize.getHeight ())
|
|
{
|
|
gridProps.autoColumns[3] = impl->scrollbarWidth;
|
|
impl->activeScrollbarStyle |= kVerticalScrollbar;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// default scrollbar sizes
|
|
gridProps.autoRows[3] = (style & kHorizontalScrollbar) ? impl->scrollbarWidth : 0.;
|
|
gridProps.autoColumns[3] = (style & kVerticalScrollbar) ? impl->scrollbarWidth : 0.;
|
|
}
|
|
|
|
impl->scrollContainer->setAutoDragScroll ((getStyle () & kAutoDragScrolling) ? true : false);
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
void CScrollView::recalculateLayout ()
|
|
{
|
|
if (!inApplyViewLayout ())
|
|
{
|
|
if (auto layout = calculateViewLayout (getViewSize ()))
|
|
applyViewLayout (*layout);
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
void CScrollView::setViewSize (const CRect &rect, bool invalid)
|
|
{
|
|
CViewContainer::setViewSize (rect, invalid);
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
void CScrollView::setAutosizeFlags (int32_t flags)
|
|
{
|
|
CViewContainer::setAutosizeFlags (flags);
|
|
if (impl->scrollContainer)
|
|
impl->scrollContainer->setAutosizeFlags (flags);
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
int32_t CScrollView::getStyle () const { return impl->style; }
|
|
|
|
//-----------------------------------------------------------------------------
|
|
void CScrollView::setStyle (int32_t newStyle)
|
|
{
|
|
if (impl->style != newStyle)
|
|
{
|
|
if ((impl->style & kDontDrawFrame) != (newStyle & kDontDrawFrame))
|
|
setBackgroundColorDrawStyle ((impl->style & kDontDrawFrame) ? kDrawFilled
|
|
: kDrawFilledAndStroked);
|
|
impl->style = newStyle;
|
|
recalculateLayout ();
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
int32_t CScrollView::getActiveScrollbars () const { return impl->activeScrollbarStyle; }
|
|
|
|
//-----------------------------------------------------------------------------
|
|
CCoord CScrollView::getScrollbarWidth () const { return impl->scrollbarWidth; }
|
|
|
|
//-----------------------------------------------------------------------------
|
|
void CScrollView::setScrollbarWidth (CCoord width)
|
|
{
|
|
if (impl->scrollbarWidth != width)
|
|
{
|
|
impl->scrollbarWidth = width;
|
|
recalculateLayout ();
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
CScrollbar* CScrollView::getVerticalScrollbar () const { return impl->vScrollbar; }
|
|
|
|
//-----------------------------------------------------------------------------
|
|
CScrollbar* CScrollView::getHorizontalScrollbar () const { return impl->hScrollbar; }
|
|
|
|
//-----------------------------------------------------------------------------
|
|
const CRect& CScrollView::getContainerSize () const { return impl->containerSize; }
|
|
|
|
//-----------------------------------------------------------------------------
|
|
void CScrollView::setContainerSize (const CRect& cs, bool keepVisibleArea)
|
|
{
|
|
vstgui_assert (impl->scrollContainer != nullptr);
|
|
|
|
impl->containerSize = cs;
|
|
impl->scrollContainer->setContainerSize (cs);
|
|
recalculateLayout ();
|
|
syncScrollbars (keepVisibleArea);
|
|
}
|
|
|
|
//------------------------------------------------------------------------
|
|
void CScrollView::syncVScrollbar (bool keepVisibleArea)
|
|
{
|
|
vstgui_assert (impl->vScrollbar != nullptr && impl->scrollContainer != nullptr);
|
|
const auto& cs = getContainerSize ();
|
|
|
|
CRect oldScrollSize = impl->vScrollbar->getScrollSize (oldScrollSize);
|
|
float oldValue = impl->vScrollbar->getValueNormalized ();
|
|
impl->vScrollbar->setScrollSize (cs);
|
|
auto csHeight = cs.getHeight ();
|
|
auto scrollContainerHeight = impl->scrollContainer->getHeight ();
|
|
auto newMax = static_cast<float> (csHeight - scrollContainerHeight);
|
|
impl->vScrollbar->setMax (newMax);
|
|
if (csHeight <= scrollContainerHeight)
|
|
impl->vScrollbar->setValueNormalized (0.f);
|
|
else if (impl->scrollContainer && keepVisibleArea && oldScrollSize.getHeight () != csHeight)
|
|
{
|
|
CRect vSize = impl->scrollContainer->getViewSize ();
|
|
float newValue =
|
|
static_cast<float> (oldValue * ((oldScrollSize.getHeight () - vSize.getHeight ()) /
|
|
(csHeight - vSize.getHeight ())));
|
|
newValue = clampNorm (newValue);
|
|
impl->vScrollbar->setValueNormalized (newValue);
|
|
}
|
|
if (impl->vScrollbar->isDirty ())
|
|
{
|
|
valueChanged (impl->vScrollbar);
|
|
impl->vScrollbar->onVisualChange ();
|
|
}
|
|
}
|
|
|
|
//------------------------------------------------------------------------
|
|
void CScrollView::syncHScrollbar (bool keepVisibleArea)
|
|
{
|
|
vstgui_assert (impl->hScrollbar != nullptr && impl->scrollContainer != nullptr);
|
|
const auto& cs = getContainerSize ();
|
|
|
|
CRect oldScrollSize = impl->hScrollbar->getScrollSize (oldScrollSize);
|
|
float oldValue = impl->hScrollbar->getValueNormalized ();
|
|
impl->hScrollbar->setScrollSize (cs);
|
|
auto csWidth = cs.getWidth ();
|
|
auto scrollContainerWidth = impl->scrollContainer->getWidth ();
|
|
auto newMax = static_cast<float> (csWidth - scrollContainerWidth);
|
|
impl->hScrollbar->setMax (newMax);
|
|
if (csWidth <= scrollContainerWidth)
|
|
impl->hScrollbar->setValueNormalized (0.f);
|
|
else if (impl->scrollContainer && keepVisibleArea && oldScrollSize.getWidth () != csWidth)
|
|
{
|
|
CRect vSize = impl->scrollContainer->getViewSize ();
|
|
float newValue =
|
|
static_cast<float> (oldValue * (oldScrollSize.getWidth () -
|
|
vSize.getWidth () / (csWidth - vSize.getWidth ())));
|
|
newValue = clampNorm (newValue);
|
|
impl->hScrollbar->setValueNormalized (newValue);
|
|
}
|
|
if (impl->hScrollbar->isDirty ())
|
|
{
|
|
valueChanged (impl->hScrollbar);
|
|
impl->hScrollbar->onVisualChange ();
|
|
}
|
|
}
|
|
|
|
//------------------------------------------------------------------------
|
|
void CScrollView::syncScrollbars (bool keepVisibleArea)
|
|
{
|
|
syncVScrollbar (keepVisibleArea);
|
|
syncHScrollbar (keepVisibleArea);
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
void CScrollView::makeRectVisible (const CRect& rect)
|
|
{
|
|
CRect r (rect);
|
|
const CPoint& scrollOffset = impl->scrollContainer->getScrollOffset ();
|
|
CPoint newOffset (scrollOffset);
|
|
CRect vs = impl->scrollContainer->getViewSize ();
|
|
vs.originize ();
|
|
CRect cs (impl->containerSize);
|
|
cs.originize ();
|
|
cs.setWidth (vs.getWidth ());
|
|
cs.setHeight (vs.getHeight ());
|
|
if (r.top >= cs.top && r.bottom <= cs.bottom && r.left >= cs.left && r.right <= cs.right)
|
|
return;
|
|
newOffset.x *= -1.;
|
|
if (r.top < cs.top)
|
|
{
|
|
newOffset.y -= (cs.top - r.top);
|
|
}
|
|
else if (r.bottom > cs.bottom)
|
|
{
|
|
newOffset.y += (r.bottom - cs.bottom);
|
|
}
|
|
if (r.left < cs.left)
|
|
{
|
|
newOffset.x += (cs.left + r.left);
|
|
}
|
|
else if (r.right > cs.right && r.left != cs.left)
|
|
{
|
|
newOffset.x += (r.right - cs.right);
|
|
}
|
|
setScrollOffset (newOffset);
|
|
}
|
|
|
|
//------------------------------------------------------------------------
|
|
void CScrollView::setScrollOffset (CPoint newOffset)
|
|
{
|
|
const CPoint& scrollOffset = impl->scrollContainer->getScrollOffset ();
|
|
CRect vs = impl->scrollContainer->getViewSize ();
|
|
vs.originize ();
|
|
if (impl->vScrollbar && newOffset.y != scrollOffset.y)
|
|
{
|
|
if (impl->containerSize.getHeight () == vs.getHeight ())
|
|
{
|
|
impl->vScrollbar->setValueNormalized (0.f);
|
|
}
|
|
else
|
|
{
|
|
impl->vScrollbar->setValue (static_cast<float> (newOffset.y));
|
|
}
|
|
impl->vScrollbar->bounceValue ();
|
|
impl->vScrollbar->onVisualChange ();
|
|
impl->vScrollbar->invalid ();
|
|
valueChanged (impl->vScrollbar);
|
|
}
|
|
if (impl->hScrollbar && newOffset.x != scrollOffset.x)
|
|
{
|
|
if (impl->containerSize.getWidth () == vs.getWidth ())
|
|
{
|
|
impl->hScrollbar->setValueNormalized (0.f);
|
|
}
|
|
else
|
|
{
|
|
impl->hScrollbar->setValue (static_cast<float> (newOffset.x));
|
|
}
|
|
impl->hScrollbar->bounceValue ();
|
|
impl->hScrollbar->onVisualChange ();
|
|
impl->hScrollbar->invalid ();
|
|
valueChanged (impl->hScrollbar);
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
void CScrollView::resetScrollOffset ()
|
|
{
|
|
if (impl->vScrollbar)
|
|
{
|
|
impl->vScrollbar->setValueNormalized (0);
|
|
impl->vScrollbar->bounceValue ();
|
|
impl->vScrollbar->onVisualChange ();
|
|
impl->vScrollbar->invalid ();
|
|
valueChanged (impl->vScrollbar);
|
|
}
|
|
if (impl->hScrollbar)
|
|
{
|
|
impl->hScrollbar->setValueNormalized (0);
|
|
impl->hScrollbar->bounceValue ();
|
|
impl->hScrollbar->onVisualChange ();
|
|
impl->hScrollbar->invalid ();
|
|
valueChanged (impl->hScrollbar);
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
const CPoint& CScrollView::getScrollOffset () const
|
|
{
|
|
return impl->scrollContainer->getScrollOffset ();
|
|
}
|
|
|
|
//------------------------------------------------------------------------
|
|
void CScrollView::setEdgeView (Edge edge, CView* _view)
|
|
{
|
|
auto view = _view;
|
|
if (view == nullptr)
|
|
view = new CView ({});
|
|
|
|
switch (edge)
|
|
{
|
|
case Edge::Top:
|
|
{
|
|
impl->edgeViewTop->unregisterViewListener (this);
|
|
CViewContainer::removeView (impl->edgeViewTop);
|
|
impl->edgeViewTop = view;
|
|
CViewContainer::addView (view, impl->edgeViewLeft);
|
|
break;
|
|
}
|
|
case Edge::Left:
|
|
{
|
|
impl->edgeViewLeft->unregisterViewListener (this);
|
|
CViewContainer::removeView (impl->edgeViewLeft);
|
|
impl->edgeViewLeft = view;
|
|
CViewContainer::addView (view, impl->vScrollbar);
|
|
break;
|
|
}
|
|
}
|
|
recalculateLayout ();
|
|
setContainerSize (impl->containerSize, true);
|
|
if (_view)
|
|
_view->registerViewListener (this);
|
|
}
|
|
|
|
//------------------------------------------------------------------------
|
|
CView* CScrollView::getEdgeView (Edge edge) const
|
|
{
|
|
CView* result = {};
|
|
switch (edge)
|
|
{
|
|
case Edge::Top:
|
|
{
|
|
result = impl->edgeViewTop;
|
|
break;
|
|
}
|
|
case Edge::Left:
|
|
{
|
|
result = impl->edgeViewLeft;
|
|
break;
|
|
}
|
|
}
|
|
if (result && result->getViewSize ().isEmpty ())
|
|
return nullptr;
|
|
return result;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
bool CScrollView::attached (CView* parent)
|
|
{
|
|
setContainerSize (impl->containerSize);
|
|
return CViewContainer::attached (parent);
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
bool CScrollView::addView (CView* pView, CView* pBefore)
|
|
{
|
|
return impl->scrollContainer->addView (pView, pBefore);
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
bool CScrollView::removeView (CView *pView, bool withForget)
|
|
{
|
|
return impl->scrollContainer->removeView (pView, withForget);
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
bool CScrollView::removeAll (bool withForget)
|
|
{
|
|
return impl->scrollContainer->removeAll (withForget);
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
uint32_t CScrollView::getNbViews () const { return impl->scrollContainer->getNbViews (); }
|
|
|
|
//-----------------------------------------------------------------------------
|
|
CView* CScrollView::getView (uint32_t index) const
|
|
{
|
|
return impl->scrollContainer->getView (index);
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
bool CScrollView::changeViewZOrder (CView* view, uint32_t newIndex)
|
|
{
|
|
return impl->scrollContainer->changeViewZOrder (view, newIndex);
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
void CScrollView::setTransparency (bool val)
|
|
{
|
|
CViewContainer::setTransparency (val);
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
void CScrollView::setBackgroundColor (const CColor& color)
|
|
{
|
|
CViewContainer::setBackgroundColor (color);
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
void CScrollView::valueChanged (CControl *pControl)
|
|
{
|
|
if (impl->scrollContainer)
|
|
{
|
|
float value = pControl->getValue ();
|
|
int32_t tag = pControl->getTag ();
|
|
CPoint offset;
|
|
CRect vsize = impl->scrollContainer->getViewSize ();
|
|
CRect csize = impl->scrollContainer->getContainerSize ();
|
|
impl->scrollContainer->getScrollOffset (offset);
|
|
|
|
switch (tag)
|
|
{
|
|
case kHSBTag:
|
|
{
|
|
if (csize.getWidth () > vsize.getWidth ())
|
|
{
|
|
offset.x = -value;
|
|
impl->scrollContainer->setScrollOffset (offset, false);
|
|
}
|
|
else if (offset.x < 0)
|
|
{
|
|
offset.x = 0;
|
|
impl->scrollContainer->setScrollOffset (offset, false);
|
|
}
|
|
break;
|
|
}
|
|
case kVSBTag:
|
|
{
|
|
if (csize.getHeight () > vsize.getHeight ())
|
|
{
|
|
offset.y = value;
|
|
impl->scrollContainer->setScrollOffset (offset, false);
|
|
}
|
|
else if (offset.y > 0)
|
|
{
|
|
offset.y = 0;
|
|
impl->scrollContainer->setScrollOffset (offset, false);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
void CScrollView::drawBackgroundRect (CDrawContext *pContext, const CRect& _updateRect)
|
|
{
|
|
CRect r (getViewSize ());
|
|
r.originize ();
|
|
CViewContainer::drawBackgroundRect (pContext, r);
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
void CScrollView::onMouseWheelEvent (MouseWheelEvent& event)
|
|
{
|
|
CViewContainer::onMouseWheelEvent (event);
|
|
if (event.consumed)
|
|
return;
|
|
if (impl->vScrollbar && event.deltaY != 0.)
|
|
impl->vScrollbar->onMouseWheelEvent (event);
|
|
if (impl->hScrollbar && event.deltaX != 0.)
|
|
impl->hScrollbar->onMouseWheelEvent (event);
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
CMessageResult CScrollView::notify (CBaseObject* sender, IdStringPtr message)
|
|
{
|
|
if (message == kMsgNewFocusView && getStyle () & kFollowFocusView)
|
|
{
|
|
auto* focusView = static_cast<CView*> (sender);
|
|
if (impl->scrollContainer->isChild (focusView, true))
|
|
{
|
|
CRect r = focusView->getViewSize ();
|
|
CPoint p;
|
|
focusView->localToFrame (p);
|
|
frameToLocal (p);
|
|
r.offset (p.x, p.y);
|
|
makeRectVisible (r);
|
|
}
|
|
}
|
|
return CViewContainer::notify (sender, message);
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
void CScrollView::viewSizeChanged (CView* view, const CRect& oldSize)
|
|
{
|
|
if (view == impl->edgeViewTop || view == impl->edgeViewLeft)
|
|
recalculateLayout ();
|
|
else if (view == impl->scrollContainer)
|
|
syncScrollbars (true);
|
|
else
|
|
{
|
|
vstgui_assert (false, "unexpected view");
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
void CScrollView::viewWillDelete (CView* view)
|
|
{
|
|
view->unregisterViewListener (this);
|
|
if (view == impl->edgeViewTop)
|
|
impl->edgeViewTop = nullptr;
|
|
else if (view == impl->edgeViewLeft)
|
|
impl->edgeViewLeft = nullptr;
|
|
}
|
|
|
|
} // VSTGUI
|