1584 lines
43 KiB
C++
1584 lines
43 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 "cviewcontainer.h"
|
|
#include "coffscreencontext.h"
|
|
#include "cbitmap.h"
|
|
#include "cframe.h"
|
|
#include "ccolor.h"
|
|
#include "ifocusdrawing.h"
|
|
#include "itouchevent.h"
|
|
#include "viewlayouter/autosizeviewlayouter.h"
|
|
#include "iviewlistener.h"
|
|
#include "controls/icontrollistener.h"
|
|
#include "cgraphicspath.h"
|
|
#include "controls/ccontrol.h"
|
|
#include "dragging.h"
|
|
#include "dispatchlist.h"
|
|
#include "events.h"
|
|
#include "finally.h"
|
|
|
|
#include <algorithm>
|
|
#include <cassert>
|
|
|
|
namespace VSTGUI {
|
|
|
|
IdStringPtr kMsgLooseFocus = "LooseFocus";
|
|
|
|
const CViewAttributeID kCViewContainerDropTargetAttribute = 'vcdt';
|
|
const CViewAttributeID kCViewContainerMouseDownViewAttribute = 'vcmd';
|
|
const CViewAttributeID kCViewContainerLastDrawnFocusAttribute = 'vclf';
|
|
const CViewAttributeID kCViewContainerBackgroundOffsetAttribute = 'vcbo';
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// CViewContainer Implementation
|
|
//-----------------------------------------------------------------------------
|
|
struct CViewContainer::Impl
|
|
{
|
|
using ViewContainerListenerDispatcher = DispatchList<IViewContainerListener*>;
|
|
|
|
ViewContainerListenerDispatcher viewContainerListeners;
|
|
CGraphicsTransform transform;
|
|
|
|
ViewList children;
|
|
SharedPointer<IViewLayouter> layouter {AutoSizeViewLayouter::get ()};
|
|
|
|
CDrawStyle backgroundColorDrawStyle {kDrawFilledAndStroked};
|
|
CColor backgroundColor {kBlackCColor};
|
|
};
|
|
|
|
//------------------------------------------------------------------------
|
|
struct CViewContainerDropTarget : public IDropTarget, public NonAtomicReferenceCounted
|
|
{
|
|
CViewContainerDropTarget (CViewContainer* container) : container (container) {}
|
|
|
|
CPoint getLocalPos (const CPoint& pos) const
|
|
{
|
|
auto viewSize = container->getViewSize ();
|
|
CPoint where2 (pos);
|
|
where2.offset (-viewSize.left, -viewSize.top);
|
|
container->getTransform ().inverse ().transform (where2);
|
|
return where2;
|
|
}
|
|
|
|
DragOperation onDragEnter (DragEventData data) final
|
|
{
|
|
assert (dropTarget == nullptr);
|
|
|
|
return onDragMove (data);
|
|
}
|
|
|
|
DragOperation onDragMove (DragEventData data) final
|
|
{
|
|
CView* view = container->getViewAt (
|
|
data.pos, GetViewOptions ().mouseEnabled ().includeViewContainer ());
|
|
data.pos = getLocalPos (data.pos);
|
|
if (view == currentDragView)
|
|
{
|
|
if (dropTarget)
|
|
return dropTarget->onDragMove (data);
|
|
return DragOperation::None;
|
|
}
|
|
if (currentDragView)
|
|
{
|
|
if (dropTarget)
|
|
dropTarget->onDragLeave (data);
|
|
dropTarget = nullptr;
|
|
currentDragView = nullptr;
|
|
}
|
|
if (view)
|
|
{
|
|
currentDragView = view;
|
|
if ((dropTarget = currentDragView->getDropTarget ()))
|
|
{
|
|
dropTarget->onDragEnter (data);
|
|
return dropTarget->onDragMove (data);
|
|
}
|
|
}
|
|
return DragOperation::None;
|
|
}
|
|
|
|
void onDragLeave (DragEventData data) final
|
|
{
|
|
if (currentDragView)
|
|
{
|
|
if (dropTarget)
|
|
{
|
|
data.pos = getLocalPos (data.pos);
|
|
dropTarget->onDragLeave (data);
|
|
dropTarget = nullptr;
|
|
}
|
|
currentDragView = nullptr;
|
|
}
|
|
}
|
|
|
|
bool onDrop (DragEventData data) final
|
|
{
|
|
if (dropTarget)
|
|
{
|
|
data.pos = getLocalPos (data.pos);
|
|
auto result = dropTarget->onDrop (data);
|
|
dropTarget = nullptr;
|
|
currentDragView = nullptr;
|
|
return result;
|
|
}
|
|
currentDragView = nullptr;
|
|
return false;
|
|
}
|
|
|
|
CViewContainer* container;
|
|
SharedPointer<IDropTarget> dropTarget;
|
|
SharedPointer<CView> currentDragView;
|
|
};
|
|
|
|
//-----------------------------------------------------------------------------
|
|
/**
|
|
* CViewContainer constructor.
|
|
* @param rect the size of the container
|
|
*/
|
|
CViewContainer::CViewContainer (const CRect &rect)
|
|
: CView (rect)
|
|
{
|
|
pImpl = std::unique_ptr<Impl> (new Impl ());
|
|
setAutosizingEnabled (true);
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
CViewContainer::CViewContainer (const CViewContainer& v)
|
|
: CView (v)
|
|
{
|
|
pImpl = std::unique_ptr<Impl> (new Impl ());
|
|
pImpl->transform = v.getTransform ();
|
|
pImpl->backgroundColorDrawStyle = v.pImpl->backgroundColorDrawStyle;
|
|
pImpl->backgroundColor = v.pImpl->backgroundColor;
|
|
setBackgroundOffset (v.getBackgroundOffset ());
|
|
for (auto& view : v.pImpl->children)
|
|
addView (static_cast<CView*> (view->newCopy ()));
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
CViewContainer::~CViewContainer () noexcept
|
|
{
|
|
vstgui_assert (pImpl->children.empty ());
|
|
vstgui_assert (pImpl->viewContainerListeners.empty ());
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
void CViewContainer::beforeDelete ()
|
|
{
|
|
IDropTarget* dropTarget = nullptr;
|
|
if (getAttribute (kCViewContainerDropTargetAttribute, dropTarget))
|
|
{
|
|
removeAttribute (kCViewContainerDropTargetAttribute);
|
|
dropTarget->forget ();
|
|
}
|
|
|
|
// remove all views
|
|
CViewContainer::removeAll (true);
|
|
CView::beforeDelete ();
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
void CViewContainer::registerViewContainerListener (IViewContainerListener* listener)
|
|
{
|
|
pImpl->viewContainerListeners.add (listener);
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
void CViewContainer::unregisterViewContainerListener (IViewContainerListener* listener)
|
|
{
|
|
pImpl->viewContainerListeners.remove (listener);
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
void CViewContainer::parentSizeChanged ()
|
|
{
|
|
for (const auto& pV : pImpl->children)
|
|
pV->parentSizeChanged (); // notify children that the size of the parent or this container has changed
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
void CViewContainer::setMouseDownView (CView* view)
|
|
{
|
|
auto mouseDownView = getMouseDownView ();
|
|
if (mouseDownView && mouseDownView != view)
|
|
{
|
|
// make sure the old mouse down view get a mouse cancel or if not implemented a mouse up
|
|
if (auto cvc = mouseDownView->asViewContainer ())
|
|
cvc->setMouseDownView (nullptr);
|
|
else
|
|
{
|
|
MouseCancelEvent cancelEvent;
|
|
mouseDownView->dispatchEvent (cancelEvent);
|
|
if (!cancelEvent.consumed)
|
|
{
|
|
MouseUpEvent upEvent;
|
|
upEvent.mousePosition = mouseDownView->getViewSize ().getTopLeft () - CPoint (10, 10);
|
|
mouseDownView->dispatchEvent (upEvent);
|
|
}
|
|
}
|
|
}
|
|
setAttribute (kCViewContainerMouseDownViewAttribute, view);
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
CView* CViewContainer::getMouseDownView () const
|
|
{
|
|
CView* view = nullptr;
|
|
if (getAttribute (kCViewContainerMouseDownViewAttribute, view))
|
|
return view;
|
|
return nullptr;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
void CViewContainer::clearMouseDownView ()
|
|
{
|
|
removeAttribute (kCViewContainerMouseDownViewAttribute);
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
CRect CViewContainer::getLastDrawnFocus () const
|
|
{
|
|
CRect r;
|
|
if (getAttribute (kCViewContainerLastDrawnFocusAttribute, r))
|
|
return r;
|
|
return {};
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
void CViewContainer::setLastDrawnFocus (CRect r)
|
|
{
|
|
if (r.isEmpty ())
|
|
removeAttribute (kCViewContainerLastDrawnFocusAttribute);
|
|
else
|
|
setAttribute (kCViewContainerLastDrawnFocusAttribute, r);
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
auto CViewContainer::getChildren () const -> const ViewList&
|
|
{
|
|
return pImpl->children;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
void CViewContainer::setTransform (const CGraphicsTransform& t)
|
|
{
|
|
if (getTransform () != t)
|
|
{
|
|
pImpl->transform = t;
|
|
pImpl->viewContainerListeners.forEach ([this] (IViewContainerListener* listener) {
|
|
listener->viewContainerTransformChanged (this);
|
|
});
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
const CGraphicsTransform& CViewContainer::getTransform () const
|
|
{
|
|
return pImpl->transform;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
void CViewContainer::setAutosizingEnabled (bool state)
|
|
{
|
|
setViewFlag (kAutosizeSubviews, state);
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
void CViewContainer::setViewLayouter (const SharedPointer<IViewLayouter>& layouter)
|
|
{
|
|
if (layouter == nullptr)
|
|
pImpl->layouter = AutoSizeViewLayouter::get ();
|
|
else
|
|
pImpl->layouter = layouter;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
SharedPointer<IViewLayouter> CViewContainer::getViewLayouter () const { return pImpl->layouter; }
|
|
|
|
//-----------------------------------------------------------------------------
|
|
std::optional<ViewLayout> CViewContainer::calculateViewLayout (const CRect& newSize) const
|
|
{
|
|
if (pImpl->layouter)
|
|
{
|
|
return pImpl->layouter->calculateLayout (*this, pImpl->children, newSize);
|
|
}
|
|
return {};
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
bool CViewContainer::applyViewLayout (const ViewLayout& layout)
|
|
{
|
|
bool result = true;
|
|
if (pImpl->layouter)
|
|
{
|
|
setViewFlag (kInApplyLayout, true);
|
|
result = pImpl->layouter->applyLayout (*this, pImpl->children, layout);
|
|
setViewFlag (kInApplyLayout, false);
|
|
}
|
|
else
|
|
{
|
|
setViewSize (layout.size);
|
|
setMouseableArea (layout.size);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
bool CViewContainer::inApplyViewLayout () const { return hasViewFlag (kInApplyLayout); }
|
|
|
|
//-----------------------------------------------------------------------------
|
|
/**
|
|
* @param rect the new size of the container
|
|
* @param invalid the views to dirty
|
|
*/
|
|
void CViewContainer::setViewSize (const CRect &rect, bool invalid)
|
|
{
|
|
if (rect == getViewSize ())
|
|
return;
|
|
|
|
if (!inApplyViewLayout () && pImpl->layouter)
|
|
{
|
|
if (auto layout = pImpl->layouter->calculateLayout (*this, pImpl->children, rect))
|
|
{
|
|
if (applyViewLayout (*layout))
|
|
{
|
|
parentSizeChanged ();
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
CView::setViewSize (rect, invalid);
|
|
|
|
parentSizeChanged ();
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
/**
|
|
* @param rect size to get visible size of
|
|
* @return visible size of rect
|
|
*/
|
|
CRect CViewContainer::getVisibleSize (const CRect& rect) const
|
|
{
|
|
CRect viewSize (getViewSize ());
|
|
getTransform ().inverse ().transform (viewSize);
|
|
CRect result (rect);
|
|
result.offset (viewSize.left, viewSize.top);
|
|
result.bound (viewSize);
|
|
if (getFrame () == this)
|
|
{}
|
|
else if (auto parent = getParentView ())
|
|
result = static_cast<CViewContainer*> (parent)->getVisibleSize (result);
|
|
result.offset (-viewSize.left, -viewSize.top);
|
|
return result;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
bool CViewContainer::sizeToFit ()
|
|
{
|
|
bool treatAsColumn = (getAutosizeFlags () & kAutosizeColumn) != 0;
|
|
bool treatAsRow = (getAutosizeFlags () & kAutosizeRow) != 0;
|
|
if (treatAsColumn || treatAsRow)
|
|
return false;
|
|
|
|
constexpr auto CoordMax = std::numeric_limits<CCoord>::max ();
|
|
constexpr auto CoordMin = -CoordMax;
|
|
CRect bounds (CoordMax, CoordMax, CoordMin, CoordMin);
|
|
for (const auto& pV : pImpl->children)
|
|
{
|
|
if (pV->isVisible ())
|
|
{
|
|
CRect vs (pV->getViewSize ());
|
|
if (vs.left < bounds.left)
|
|
bounds.left = vs.left;
|
|
if (vs.right > bounds.right)
|
|
bounds.right = vs.right;
|
|
if (vs.top < bounds.top)
|
|
bounds.top = vs.top;
|
|
if (vs.bottom > bounds.bottom)
|
|
bounds.bottom = vs.bottom;
|
|
}
|
|
}
|
|
|
|
if (bounds == CRect (CoordMax, CoordMax, CoordMin, CoordMin))
|
|
return false;
|
|
|
|
CRect vs (getViewSize ());
|
|
vs.right = vs.left + bounds.right + bounds.left;
|
|
vs.bottom = vs.top + bounds.bottom + bounds.top;
|
|
|
|
setViewSize (vs);
|
|
setMouseableArea (vs);
|
|
|
|
return true;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
/**
|
|
* @param color the new background color of the container
|
|
*/
|
|
void CViewContainer::setBackgroundColor (const CColor& color)
|
|
{
|
|
if (color != pImpl->backgroundColor)
|
|
{
|
|
pImpl->backgroundColor = color;
|
|
setDirty (true);
|
|
}
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
CColor CViewContainer::getBackgroundColor () const
|
|
{
|
|
return pImpl->backgroundColor;
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
void CViewContainer::setBackgroundOffset (const CPoint& p)
|
|
{
|
|
if (p == CPoint (0, 0))
|
|
removeAttribute (kCViewContainerBackgroundOffsetAttribute);
|
|
else
|
|
setAttribute (kCViewContainerBackgroundOffsetAttribute, p);
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
CPoint CViewContainer::getBackgroundOffset () const
|
|
{
|
|
CPoint p;
|
|
if (getAttribute (kCViewContainerBackgroundOffsetAttribute, p))
|
|
return p;
|
|
return {};
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
void CViewContainer::setBackgroundColorDrawStyle (CDrawStyle style)
|
|
{
|
|
if (pImpl->backgroundColorDrawStyle != style)
|
|
{
|
|
pImpl->backgroundColorDrawStyle = style;
|
|
setDirty (true);
|
|
}
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
CDrawStyle CViewContainer::getBackgroundColorDrawStyle () const
|
|
{
|
|
return pImpl->backgroundColorDrawStyle;
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
CMessageResult CViewContainer::notify (CBaseObject* sender, IdStringPtr message)
|
|
{
|
|
if (message == kMsgNewFocusView)
|
|
{
|
|
auto* view = dynamic_cast<CView*> (sender);
|
|
if (view && isChild (view, false) && getFrame ()->focusDrawingEnabled ())
|
|
{
|
|
CCoord width = getFrame ()->getFocusWidth ();
|
|
CRect viewSize (view->getViewSize ());
|
|
viewSize.extend (width, width);
|
|
invalidRect (viewSize);
|
|
}
|
|
}
|
|
else if (message == kMsgOldFocusView)
|
|
{
|
|
auto ldf = getLastDrawnFocus ();
|
|
if (!ldf.isEmpty ())
|
|
{
|
|
invalidRect (ldf);
|
|
setLastDrawnFocus (CRect (0, 0, 0, 0));
|
|
}
|
|
}
|
|
return kMessageUnknown;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
/**
|
|
* @param pView the view object to add to this container
|
|
* @param pBefore the view object
|
|
* @return true on success. false if view was already attached
|
|
*/
|
|
bool CViewContainer::addView (CView *pView, CView* pBefore)
|
|
{
|
|
if (!pView)
|
|
return false;
|
|
|
|
vstgui_assert (!pView->isSubview (), "view is already added to a container view");
|
|
|
|
if (pBefore)
|
|
{
|
|
auto it = std::find (pImpl->children.begin (), pImpl->children.end (), pBefore);
|
|
vstgui_assert (it != pImpl->children.end ());
|
|
pImpl->children.insert (it, pView);
|
|
}
|
|
else
|
|
{
|
|
pImpl->children.emplace_back (pView);
|
|
}
|
|
|
|
pView->setSubviewState (true);
|
|
|
|
pImpl->viewContainerListeners.forEach ([&] (IViewContainerListener* listener) {
|
|
listener->viewContainerViewAdded (this, pView);
|
|
});
|
|
|
|
if (isAttached ())
|
|
{
|
|
pView->attached (this);
|
|
pView->invalid ();
|
|
}
|
|
return true;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
/**
|
|
* @param pView the view object to add to this container
|
|
* @param mouseableArea the view area in where the view will get mouse events
|
|
* @param mouseEnabled bool to set if view will get mouse events
|
|
* @return true on success. false if view was already attached
|
|
*/
|
|
bool CViewContainer::addView (CView* pView, const CRect &mouseableArea, bool mouseEnabled)
|
|
{
|
|
if (addView (pView, nullptr))
|
|
{
|
|
pView->setMouseEnabled (mouseEnabled);
|
|
pView->setMouseableArea (mouseableArea);
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
/**
|
|
* @param withForget bool to indicate if the view's reference counter should be decreased after removed from the container
|
|
* @return true on success
|
|
*/
|
|
bool CViewContainer::removeAll (bool withForget)
|
|
{
|
|
clearMouseDownView ();
|
|
setInitialFocusView (nullptr);
|
|
|
|
auto it = pImpl->children.begin ();
|
|
while (it != pImpl->children.end ())
|
|
{
|
|
auto view = *it;
|
|
if (isAttached ())
|
|
view->removed (this);
|
|
pImpl->children.erase (it);
|
|
view->setSubviewState (false);
|
|
pImpl->viewContainerListeners.forEach ([&] (IViewContainerListener* listener) {
|
|
listener->viewContainerViewRemoved (this, view);
|
|
});
|
|
if (withForget)
|
|
view->forget ();
|
|
it = pImpl->children.begin ();
|
|
}
|
|
return true;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
/**
|
|
* @param pView the view which should be removed from the container
|
|
* @param withForget bool to indicate if the view's reference counter should be decreased after removed from the container
|
|
* @return true on success
|
|
*/
|
|
bool CViewContainer::removeView (CView *pView, bool withForget)
|
|
{
|
|
auto it = std::find (pImpl->children.begin (), pImpl->children.end (), pView);
|
|
if (it != pImpl->children.end ())
|
|
{
|
|
if (pView == getInitialFocusView ())
|
|
setInitialFocusView (nullptr);
|
|
pView->invalid ();
|
|
if (pView == getMouseDownView ())
|
|
clearMouseDownView ();
|
|
if (isAttached ())
|
|
pView->removed (this);
|
|
pView->setSubviewState (false);
|
|
pImpl->viewContainerListeners.forEach ([&] (IViewContainerListener* listener) {
|
|
listener->viewContainerViewRemoved (this, pView);
|
|
});
|
|
if (withForget)
|
|
pView->forget ();
|
|
pImpl->children.erase (it);
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
/**
|
|
* @param pView the view which should be checked if it is a child of this container
|
|
* @return true on success
|
|
*/
|
|
bool CViewContainer::isChild (CView* pView) const
|
|
{
|
|
return isChild (pView, false);
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
bool CViewContainer::isChild (CView *pView, bool deep) const
|
|
{
|
|
bool found = false;
|
|
|
|
if (deep)
|
|
{
|
|
auto it = pImpl->children.begin ();
|
|
while (!found && it != pImpl->children.end ())
|
|
{
|
|
CView* v = (*it);
|
|
if (pView == v)
|
|
{
|
|
found = true;
|
|
break;
|
|
}
|
|
if (CViewContainer* container = v->asViewContainer ())
|
|
found = container->isChild (pView, true);
|
|
++it;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
found = std::find (pImpl->children.begin (), pImpl->children.end (), pView) != pImpl->children.end ();
|
|
}
|
|
return found;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
bool CViewContainer::hasChildren () const
|
|
{
|
|
return !pImpl->children.empty ();
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
/**
|
|
* @return number of subviews
|
|
*/
|
|
uint32_t CViewContainer::getNbViews () const
|
|
{
|
|
return static_cast<uint32_t> (pImpl->children.size ());
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
/**
|
|
* @param index the index of the view to return
|
|
* @return view at index. NULL if view at index does not exist.
|
|
*/
|
|
CView* CViewContainer::getView (uint32_t index) const
|
|
{
|
|
auto it = pImpl->children.begin ();
|
|
std::advance (it, index);
|
|
if (it != pImpl->children.end ())
|
|
return *it;
|
|
return nullptr;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
/**
|
|
* @param view view which z order position should be changed
|
|
* @param newIndex index of new z position
|
|
* @return true if z order of view changed
|
|
*/
|
|
bool CViewContainer::changeViewZOrder (CView* view, uint32_t newIndex)
|
|
{
|
|
if (newIndex < getNbViews ())
|
|
{
|
|
uint32_t oldIndex = 0;
|
|
auto src = pImpl->children.begin ();
|
|
for (;src != pImpl->children.end () && *src != view; ++src, ++oldIndex);
|
|
if (src != pImpl->children.end ())
|
|
{
|
|
if (newIndex == oldIndex)
|
|
return true;
|
|
if (newIndex > oldIndex)
|
|
++newIndex;
|
|
|
|
auto dest = pImpl->children.begin ();
|
|
std::advance (dest, newIndex);
|
|
|
|
pImpl->children.insert (dest, view);
|
|
pImpl->children.erase (src);
|
|
|
|
pImpl->viewContainerListeners.forEach ([&] (IViewContainerListener* listener) {
|
|
listener->viewContainerViewZOrderChanged (this, view);
|
|
});
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
bool CViewContainer::invalidateDirtyViews ()
|
|
{
|
|
if (!isVisible ())
|
|
return true;
|
|
if (CView::isDirty ())
|
|
{
|
|
if (auto parent = getParentView ())
|
|
parent->invalidRect (getViewSize ());
|
|
return true;
|
|
}
|
|
for (const auto& pV : pImpl->children)
|
|
{
|
|
if (pV->isDirty () && pV->isVisible ())
|
|
{
|
|
if (CViewContainer* container = pV->asViewContainer ())
|
|
container->invalidateDirtyViews ();
|
|
else
|
|
pV->invalid ();
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
void CViewContainer::invalid ()
|
|
{
|
|
if (!isVisible ())
|
|
return;
|
|
CRect _rect (getViewSize ());
|
|
if (auto parent = getParentView ())
|
|
parent->invalidRect (_rect);
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
void CViewContainer::invalidRect (const CRect& rect)
|
|
{
|
|
if (!isVisible ())
|
|
return;
|
|
CRect _rect (rect);
|
|
getTransform ().transform (_rect);
|
|
_rect.offset (getViewSize ().left, getViewSize ().top);
|
|
_rect.bound (getViewSize ());
|
|
if (_rect.isEmpty ())
|
|
return;
|
|
if (auto parent = getParentView ())
|
|
parent->invalidRect (_rect);
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
/**
|
|
* @param pContext the context which to use to draw this container and its subviews
|
|
*/
|
|
void CViewContainer::draw (CDrawContext* pContext)
|
|
{
|
|
CViewContainer::drawRect (pContext, getViewSize ());
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
/**
|
|
* @param pContext the context which to use to draw the background
|
|
* @param _updateRect the area which to draw
|
|
*/
|
|
void CViewContainer::drawBackgroundRect (CDrawContext* pContext, const CRect& _updateRect)
|
|
{
|
|
if (getDrawBackground ())
|
|
{
|
|
drawClipped (pContext, _updateRect, [&] () {
|
|
CRect tr (0, 0, getViewSize ().getWidth (), getViewSize ().getHeight ());
|
|
getDrawBackground ()->draw (pContext, tr, getBackgroundOffset ());
|
|
});
|
|
}
|
|
else if ((pImpl->backgroundColor.alpha != 255 && getTransparency ()) || !getTransparency ())
|
|
{
|
|
pContext->setDrawMode (kAliasing);
|
|
pContext->setLineWidth (1);
|
|
pContext->setFillColor (pImpl->backgroundColor);
|
|
pContext->setFrameColor (pImpl->backgroundColor);
|
|
pContext->setLineStyle (kLineSolid);
|
|
CRect r;
|
|
if (pImpl->backgroundColorDrawStyle == kDrawFilled || (pImpl->backgroundColorDrawStyle == kDrawFilledAndStroked && pImpl->backgroundColor.alpha == 255))
|
|
{
|
|
r = _updateRect;
|
|
r.extend (1, 1);
|
|
}
|
|
else
|
|
{
|
|
r = getViewSize ();
|
|
r.offset (-r.left, -r.top);
|
|
}
|
|
pContext->drawRect (r, pImpl->backgroundColorDrawStyle);
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
/**
|
|
* @param pContext the context which to use to draw
|
|
* @param updateRect the area which to draw
|
|
*/
|
|
void CViewContainer::drawRect (CDrawContext* pContext, const CRect& updateRect)
|
|
{
|
|
CPoint offset (getViewSize ().left, getViewSize ().top);
|
|
CDrawContext::Transform offsetTransform (*pContext, CGraphicsTransform ().translate (offset.x, offset.y));
|
|
|
|
CRect _updateRect (updateRect);
|
|
_updateRect.bound (getViewSize ());
|
|
|
|
CRect clientRect (_updateRect);
|
|
clientRect.offset (-getViewSize ().left, -getViewSize ().top);
|
|
|
|
CRect oldClip;
|
|
pContext->getClipRect (oldClip);
|
|
CRect oldClip2 (oldClip);
|
|
|
|
CRect newClip (clientRect);
|
|
newClip.bound (oldClip);
|
|
pContext->setClipRect (newClip);
|
|
|
|
// draw the background
|
|
drawBackgroundRect (pContext, clientRect);
|
|
|
|
CView* _focusView = nullptr;
|
|
IFocusDrawing* _focusDrawing = nullptr;
|
|
auto frame = getFrame ();
|
|
if (frame && frame->focusDrawingEnabled () && isChild (frame->getFocusView (), false) && frame->getFocusView ()->isVisible () && frame->getFocusView ()->wantsFocus ())
|
|
{
|
|
_focusView = frame->getFocusView ();
|
|
_focusDrawing = dynamic_cast<IFocusDrawing*> (_focusView);
|
|
}
|
|
|
|
{
|
|
CDrawContext::Transform tr (*pContext, getTransform ());
|
|
getTransform ().inverse ().transform (newClip);
|
|
getTransform ().inverse ().transform (clientRect);
|
|
getTransform ().transform (oldClip2);
|
|
|
|
// draw each view
|
|
for (const auto& pV : pImpl->children)
|
|
{
|
|
if (pV->isVisible ())
|
|
{
|
|
if (frame && _focusDrawing && _focusView == pV && !_focusDrawing->drawFocusOnTop ())
|
|
{
|
|
SharedPointer<CGraphicsPath> focusPath = owned (pContext->createGraphicsPath ());
|
|
if (focusPath)
|
|
{
|
|
if (_focusDrawing->getFocusPath (*focusPath))
|
|
{
|
|
auto lastDrawnFocus = focusPath->getBoundingBox ();
|
|
if (!lastDrawnFocus.isEmpty ())
|
|
{
|
|
pContext->setClipRect (oldClip2);
|
|
pContext->setDrawMode (kAntiAliasing|kNonIntegralMode);
|
|
pContext->setFillColor (frame->getFocusColor ());
|
|
pContext->drawGraphicsPath (focusPath, CDrawContext::kPathFilledEvenOdd);
|
|
lastDrawnFocus.extend (1, 1);
|
|
setLastDrawnFocus (lastDrawnFocus);
|
|
}
|
|
_focusDrawing = nullptr;
|
|
_focusView = nullptr;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (checkUpdateRect (pV, clientRect))
|
|
{
|
|
CRect viewSize = pV->getViewSize ();
|
|
viewSize.bound (newClip);
|
|
if (viewSize.getWidth () == 0 || viewSize.getHeight () == 0)
|
|
continue;
|
|
pContext->setClipRect (viewSize);
|
|
float globalContextAlpha = pContext->getGlobalAlpha ();
|
|
pContext->setGlobalAlpha (globalContextAlpha * pV->getAlphaValue ());
|
|
pV->drawRect (pContext, viewSize);
|
|
pContext->setGlobalAlpha (globalContextAlpha);
|
|
#if DEBUG
|
|
static bool drawViewWireFrames = false;
|
|
if (drawViewWireFrames)
|
|
{
|
|
pContext->setFrameColor (kRedCColor);
|
|
pContext->setLineWidth (pContext->getHairlineSize ());
|
|
pContext->setLineStyle (kLineSolid);
|
|
pContext->drawRect (pV->getViewSize ());
|
|
}
|
|
#endif
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
pContext->setClipRect (oldClip2);
|
|
|
|
if (frame && _focusView)
|
|
{
|
|
SharedPointer<CGraphicsPath> focusPath = owned (pContext->createGraphicsPath ());
|
|
if (focusPath)
|
|
{
|
|
if (_focusDrawing)
|
|
_focusDrawing->getFocusPath (*focusPath);
|
|
else
|
|
{
|
|
CCoord focusWidth = frame->getFocusWidth ();
|
|
CRect r (_focusView->getVisibleViewSize ());
|
|
if (!r.isEmpty ())
|
|
{
|
|
focusPath->addRect (r);
|
|
r.extend (focusWidth, focusWidth);
|
|
focusPath->addRect (r);
|
|
}
|
|
}
|
|
auto lastDrawnFocus = focusPath->getBoundingBox ();
|
|
if (!lastDrawnFocus.isEmpty ())
|
|
{
|
|
pContext->setDrawMode (kAntiAliasing|kNonIntegralMode);
|
|
pContext->setFillColor (frame->getFocusColor ());
|
|
pContext->drawGraphicsPath (focusPath, CDrawContext::kPathFilledEvenOdd);
|
|
lastDrawnFocus.extend (1, 1);
|
|
setLastDrawnFocus (lastDrawnFocus);
|
|
}
|
|
}
|
|
}
|
|
|
|
setDirty (false);
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
/**
|
|
* check if view needs to be updated for rect
|
|
* @param view view to check
|
|
* @param rect update rect
|
|
* @return true if view needs update
|
|
*/
|
|
bool CViewContainer::checkUpdateRect (CView* view, const CRect& rect)
|
|
{
|
|
return view->checkUpdate (rect) && view->isVisible ();
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
/**
|
|
* @param where point
|
|
* @param event current event
|
|
* @return true if any sub view accepts the hit
|
|
*/
|
|
bool CViewContainer::hitTestSubViews (const CPoint& where, const Event& event)
|
|
{
|
|
CPoint where2 (where);
|
|
where2.offset (-getViewSize ().left, -getViewSize ().top);
|
|
getTransform ().inverse ().transform (where2);
|
|
|
|
for (auto it = pImpl->children.rbegin (), end = pImpl->children.rend (); it != end; ++it)
|
|
{
|
|
const auto& pV = *it;
|
|
if (pV && pV->isVisible () && pV->getMouseEnabled () && pV->hitTest (where2, event))
|
|
{
|
|
if (auto container = pV->asViewContainer ())
|
|
{
|
|
if (container->hitTestSubViews (where2, event))
|
|
return true;
|
|
}
|
|
else
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
#if VSTGUI_ENABLE_DEPRECATED_METHODS
|
|
//------------------------------------------------------------------------
|
|
bool CViewContainer::onWheel (const CPoint& where, const CMouseWheelAxis& axis,
|
|
const float& distance, const CButtonState& buttons)
|
|
{
|
|
return false;
|
|
}
|
|
#endif
|
|
|
|
//------------------------------------------------------------------------
|
|
void CViewContainer::dispatchEventToSubViews (Event& event)
|
|
{
|
|
if (auto mouseEvent = asMousePositionEvent (event))
|
|
{
|
|
auto mousePos = mouseEvent->mousePosition;
|
|
auto f = finally ([&] () { mouseEvent->mousePosition = mousePos; });
|
|
mouseEvent->mousePosition.offset (-getViewSize ().left, -getViewSize ().top);
|
|
getTransform ().inverse ().transform (mouseEvent->mousePosition);
|
|
for (auto it = pImpl->children.rbegin (), end = pImpl->children.rend (); it != end;
|
|
++it)
|
|
{
|
|
const auto& pV = *it;
|
|
if (pV && pV->isVisible () && pV->getMouseEnabled () &&
|
|
pV->getMouseableArea ().pointInside (mouseEvent->mousePosition))
|
|
{
|
|
pV->dispatchEvent (event);
|
|
if (!pV->getTransparency () || event.consumed)
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
//------------------------------------------------------------------------
|
|
void CViewContainer::onMouseWheelEvent (MouseWheelEvent& event)
|
|
{
|
|
dispatchEventToSubViews (event);
|
|
}
|
|
|
|
//------------------------------------------------------------------------
|
|
void CViewContainer::onZoomGestureEvent (ZoomGestureEvent& event)
|
|
{
|
|
dispatchEventToSubViews (event);
|
|
}
|
|
|
|
//------------------------------------------------------------------------
|
|
void CViewContainer::onMouseDownEvent (MouseDownEvent& event)
|
|
{
|
|
auto buttonState = buttonStateFromMouseEvent (event);
|
|
auto mouseResult = onMouseDown (event.mousePosition, buttonState);
|
|
if (!(mouseResult == kMouseEventNotHandled || mouseResult == kMouseEventNotImplemented))
|
|
{
|
|
event.consumed = true;
|
|
if (mouseResult == kMouseMoveEventHandledButDontNeedMoreEvents)
|
|
event.ignoreFollowUpMoveAndUpEvents (true);
|
|
return;
|
|
}
|
|
|
|
auto f = finally ([&, pos = event.mousePosition] () { event.mousePosition = pos; });
|
|
event.mousePosition.offset (-getViewSize ().left, -getViewSize ().top);
|
|
getTransform ().inverse ().transform (event.mousePosition);
|
|
for (auto it = pImpl->children.rbegin (), end = pImpl->children.rend (); it != end; ++it)
|
|
{
|
|
const auto& pV = *it;
|
|
if (pV && pV->isVisible () && pV->getMouseEnabled () &&
|
|
pV->hitTest (event.mousePosition, event))
|
|
{
|
|
if (!event.modifiers.empty ())
|
|
{
|
|
if (auto control = pV.cast<CControl> ())
|
|
{
|
|
if (auto listener = control->getListener ())
|
|
{
|
|
if (listener->controlModifierClicked (control, buttonState) != 0)
|
|
{
|
|
event.consumed = true;
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
auto frame = getFrame ();
|
|
auto previousFocusView = frame ? frame->getFocusView () : nullptr;
|
|
#if VSTGUI_ENABLE_DEPRECATED_METHODS
|
|
mouseResult = pV->callMouseListener (MouseListenerCall::MouseDown, event.mousePosition, buttonState);
|
|
if (!(mouseResult == kMouseEventNotHandled || mouseResult == kMouseEventNotImplemented))
|
|
{
|
|
event.consumed = true;
|
|
if (mouseResult == kMouseMoveEventHandledButDontNeedMoreEvents)
|
|
event.ignoreFollowUpMoveAndUpEvents (true);
|
|
return;
|
|
}
|
|
#endif
|
|
pV->dispatchEvent (event);
|
|
if (event.consumed)
|
|
{
|
|
if (pV->getNbReference () >1)
|
|
{
|
|
if (pV->wantsFocus () && frame && frame->getFocusView () == previousFocusView &&
|
|
dynamic_cast<CControl*> (pV.get ()))
|
|
{
|
|
getFrame ()->setFocusView (pV);
|
|
}
|
|
if (!event.ignoreFollowUpMoveAndUpEvents ())
|
|
setMouseDownView (pV);
|
|
}
|
|
return;
|
|
}
|
|
if (!pV->getTransparency ())
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
//------------------------------------------------------------------------
|
|
void CViewContainer::onMouseMoveEvent (MouseMoveEvent& event)
|
|
{
|
|
auto buttonState = buttonStateFromMouseEvent (event);
|
|
auto mouseResult = onMouseMoved (event.mousePosition, buttonState);
|
|
if (!(mouseResult == kMouseEventNotHandled || mouseResult == kMouseEventNotImplemented))
|
|
{
|
|
event.consumed = true;
|
|
if (mouseResult == kMouseMoveEventHandledButDontNeedMoreEvents)
|
|
event.ignoreFollowUpMoveAndUpEvents (true);
|
|
return;
|
|
}
|
|
if (auto view = shared (getMouseDownView ()))
|
|
{
|
|
auto f = finally ([&, pos = event.mousePosition] () { event.mousePosition = pos; });
|
|
event.mousePosition.offset (-getViewSize ().left, -getViewSize ().top);
|
|
getTransform ().inverse ().transform (event.mousePosition);
|
|
#if VSTGUI_ENABLE_DEPRECATED_METHODS
|
|
mouseResult = view->callMouseListener (MouseListenerCall::MouseMoved, event.mousePosition,
|
|
buttonState);
|
|
if (!(mouseResult == kMouseEventNotHandled || mouseResult == kMouseEventNotImplemented))
|
|
{
|
|
event.consumed = true;
|
|
if (mouseResult == kMouseMoveEventHandledButDontNeedMoreEvents)
|
|
event.ignoreFollowUpMoveAndUpEvents (true);
|
|
return;
|
|
}
|
|
#endif
|
|
view->dispatchEvent (event);
|
|
}
|
|
}
|
|
|
|
//------------------------------------------------------------------------
|
|
void CViewContainer::onMouseUpEvent (MouseUpEvent& event)
|
|
{
|
|
auto buttonState = buttonStateFromMouseEvent (event);
|
|
auto mouseResult = onMouseUp (event.mousePosition, buttonState);
|
|
if (!(mouseResult == kMouseEventNotHandled || mouseResult == kMouseEventNotImplemented))
|
|
{
|
|
event.consumed = true;
|
|
return;
|
|
}
|
|
if (auto view = shared (getMouseDownView ()))
|
|
{
|
|
auto f = finally ([&, pos = event.mousePosition] () { event.mousePosition = pos; });
|
|
event.mousePosition.offset (-getViewSize ().left, -getViewSize ().top);
|
|
getTransform ().inverse ().transform (event.mousePosition);
|
|
#if VSTGUI_ENABLE_DEPRECATED_METHODS
|
|
mouseResult =
|
|
view->callMouseListener (MouseListenerCall::MouseUp, event.mousePosition, buttonState);
|
|
if (!(mouseResult == kMouseEventNotHandled || mouseResult == kMouseEventNotImplemented))
|
|
{
|
|
event.consumed = true;
|
|
return;
|
|
}
|
|
#endif
|
|
view->dispatchEvent (event);
|
|
clearMouseDownView ();
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
void CViewContainer::onMouseCancelEvent (MouseCancelEvent& event)
|
|
{
|
|
if (auto mouseDownView = getMouseDownView ())
|
|
{
|
|
CBaseObjectGuard crg (mouseDownView);
|
|
#if VSTGUI_ENABLE_DEPRECATED_METHODS
|
|
mouseDownView->callMouseListener (MouseListenerCall::MouseCancel, {}, 0);
|
|
#endif
|
|
mouseDownView->dispatchEvent (event);
|
|
clearMouseDownView ();
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
SharedPointer<IDropTarget> CViewContainer::getDropTarget ()
|
|
{
|
|
if (getFrame () == this)
|
|
{
|
|
IDropTarget* dropTarget = nullptr;
|
|
if (!getAttribute (kCViewContainerDropTargetAttribute, dropTarget))
|
|
{
|
|
dropTarget = new CViewContainerDropTarget (this);
|
|
setAttribute (kCViewContainerDropTargetAttribute, dropTarget);
|
|
}
|
|
return dropTarget;
|
|
}
|
|
if (auto customDropTarget = CView::getDropTarget ())
|
|
return customDropTarget;
|
|
return makeOwned<CViewContainerDropTarget> (this);
|
|
}
|
|
|
|
#if VSTGUI_TOUCH_EVENT_HANDLING
|
|
//-----------------------------------------------------------------------------
|
|
void CViewContainer::onTouchEvent (ITouchEvent& event)
|
|
{
|
|
ReverseViewIterator it (this);
|
|
while (*it)
|
|
{
|
|
CView* view = *it;
|
|
CBaseObjectGuard guard (view);
|
|
if (view->wantsMultiTouchEvents ())
|
|
{
|
|
for (const auto& e : event)
|
|
{
|
|
if (e.second.state == ITouchEvent::kBegan && e.second.target == 0)
|
|
{
|
|
CPoint where (e.second.location);
|
|
frameToLocal (where);
|
|
MouseDownEvent downEvent (where, MouseButton::Left);
|
|
downEvent.clickCount = e.second.tapCount;
|
|
if (view->hitTest (where, downEvent))
|
|
{
|
|
view->onTouchEvent (event);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
it++;
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
bool CViewContainer::findSingleTouchEventTarget (ITouchEvent::Touch& event)
|
|
{
|
|
vstgui_assert(event.target == 0);
|
|
vstgui_assert(event.state == ITouchEvent::kBegan);
|
|
|
|
CPoint where (event.location);
|
|
frameToLocal (where);
|
|
|
|
MouseDownEvent downEvent (where, MouseButton::Left);
|
|
downEvent.clickCount = event.tapCount;
|
|
|
|
ReverseViewIterator it (this);
|
|
while (*it)
|
|
{
|
|
CView* view = *it;
|
|
CBaseObjectGuard guard (view);
|
|
if (view->getMouseEnabled () && view->isVisible () && view->hitTest (where, downEvent))
|
|
{
|
|
if (auto container = view->asViewContainer ())
|
|
{
|
|
if (container->findSingleTouchEventTarget (event))
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
view->dispatchEvent (downEvent);
|
|
if (downEvent.ignoreFollowUpMoveAndUpEvents ())
|
|
return true;
|
|
else if (downEvent.consumed)
|
|
{
|
|
event.target = view;
|
|
event.targetIsSingleTouch = true;
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
it++;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
#endif
|
|
|
|
//-----------------------------------------------------------------------------
|
|
void CViewContainer::looseFocus ()
|
|
{
|
|
CView::looseFocus ();
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
void CViewContainer::takeFocus ()
|
|
{
|
|
CView::takeFocus ();
|
|
}
|
|
|
|
//------------------------------------------------------------------------
|
|
void CViewContainer::setInitialFocusView (CView* view)
|
|
{
|
|
if (auto oldInitialFocusView = getInitialFocusView ())
|
|
{
|
|
oldInitialFocusView->forget ();
|
|
}
|
|
if (view)
|
|
{
|
|
setAttribute (kInitialFocusViewAttribute, view);
|
|
view->remember ();
|
|
}
|
|
else
|
|
{
|
|
removeAttribute (kInitialFocusViewAttribute);
|
|
}
|
|
}
|
|
|
|
//------------------------------------------------------------------------
|
|
CView* CViewContainer::getInitialFocusView () const
|
|
{
|
|
CView* initialFocusView = nullptr;
|
|
getAttribute (kInitialFocusViewAttribute, initialFocusView);
|
|
return initialFocusView;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
/**
|
|
* @param oldFocus old focus view
|
|
* @param reverse search order
|
|
* @return true on success
|
|
*/
|
|
bool CViewContainer::advanceNextFocusView (CView* oldFocus, bool reverse)
|
|
{
|
|
if (auto frame = getFrame ())
|
|
{
|
|
if (!oldFocus)
|
|
{
|
|
if (auto initialFocusView = getInitialFocusView ())
|
|
{
|
|
frame->setFocusView (initialFocusView);
|
|
return true;
|
|
}
|
|
}
|
|
|
|
bool foundOld = false;
|
|
|
|
auto func = [&] (CView* pV) {
|
|
if (oldFocus && !foundOld)
|
|
{
|
|
if (oldFocus == pV)
|
|
{
|
|
foundOld = true;
|
|
return false;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (pV->wantsFocus () && pV->getMouseEnabled () && pV->isVisible ())
|
|
{
|
|
frame->setFocusView (pV);
|
|
return true;
|
|
}
|
|
else if (CViewContainer* container = pV->asViewContainer ())
|
|
{
|
|
if (container->advanceNextFocusView (nullptr, reverse))
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
};
|
|
|
|
if (reverse)
|
|
{
|
|
for (auto it = pImpl->children.rbegin (), end = pImpl->children.rend (); it != end; ++it)
|
|
{
|
|
if (func (*it))
|
|
return true;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
for (const auto& view : pImpl->children)
|
|
{
|
|
if (func (view))
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
bool CViewContainer::isDirty () const
|
|
{
|
|
if (CView::isDirty ())
|
|
return true;
|
|
|
|
CRect viewSize (getViewSize ());
|
|
viewSize.offset (-getViewSize ().left, -getViewSize ().top);
|
|
|
|
for (const auto& pV : pImpl->children)
|
|
{
|
|
if (pV->isDirty () && pV->isVisible ())
|
|
{
|
|
CRect r = pV->getViewSize ();
|
|
r.bound (viewSize);
|
|
if (r.getWidth () > 0 && r.getHeight () > 0)
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
/**
|
|
* @param p location
|
|
* @param options search options
|
|
* @return view at position p or null
|
|
*/
|
|
CView* CViewContainer::getViewAt (const CPoint& p, const GetViewOptions& options) const
|
|
{
|
|
CPoint where (p);
|
|
where.offset (-getViewSize ().left, -getViewSize ().top);
|
|
getTransform ().inverse ().transform (where);
|
|
|
|
for (auto it = pImpl->children.rbegin (), end = pImpl->children.rend (); it != end; ++it)
|
|
{
|
|
const auto& pV = *it;
|
|
if (pV && pV->getMouseableArea ().pointInside (where))
|
|
{
|
|
if (!options.getIncludeInvisible () && pV->isVisible () == false)
|
|
continue;
|
|
if (options.getMouseEnabled ())
|
|
{
|
|
if (pV->getMouseEnabled () == false)
|
|
continue;
|
|
}
|
|
if (options.getDeep ())
|
|
{
|
|
if (auto container = pV->asViewContainer ())
|
|
{
|
|
CView* view = container->getViewAt (where, options);
|
|
return options.getIncludeViewContainer () ? (view ? view : container) : view;
|
|
}
|
|
}
|
|
if (!options.getIncludeViewContainer () && pV->asViewContainer ())
|
|
continue;
|
|
return pV;
|
|
}
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
/**
|
|
* @param p location
|
|
* @param views result list
|
|
* @param options search options
|
|
* @return success
|
|
*/
|
|
bool CViewContainer::getViewsAt (const CPoint& p, ViewList& views, const GetViewOptions& options) const
|
|
{
|
|
bool result = false;
|
|
|
|
CPoint where (p);
|
|
where.offset (-getViewSize ().left, -getViewSize ().top);
|
|
getTransform ().inverse ().transform (where);
|
|
|
|
for (auto it = pImpl->children.rbegin (), end = pImpl->children.rend (); it != end; ++it)
|
|
{
|
|
const auto& pV = *it;
|
|
if (pV && pV->getMouseableArea ().pointInside (where))
|
|
{
|
|
if (!options.getIncludeInvisible () && pV->isVisible () == false)
|
|
continue;
|
|
if (options.getMouseEnabled ())
|
|
{
|
|
if (pV->getMouseEnabled () == false)
|
|
continue;
|
|
}
|
|
if (options.getDeep ())
|
|
{
|
|
if (CViewContainer* container = pV->asViewContainer ())
|
|
result |= container->getViewsAt (where, views, options);
|
|
}
|
|
if (options.getIncludeViewContainer () == false)
|
|
{
|
|
if (pV->asViewContainer ())
|
|
continue;
|
|
}
|
|
views.emplace_back (pV);
|
|
result = true;
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
/**
|
|
* @param p location
|
|
* @param options search search options
|
|
* @return view container at position p or null
|
|
*/
|
|
CViewContainer* CViewContainer::getContainerAt (const CPoint& p, const GetViewOptions& options) const
|
|
{
|
|
CPoint where (p);
|
|
where.offset (-getViewSize ().left, -getViewSize ().top);
|
|
getTransform ().inverse ().transform (where);
|
|
|
|
for (auto it = pImpl->children.rbegin (), end = pImpl->children.rend (); it != end; ++it)
|
|
{
|
|
const auto& pV = *it;
|
|
if (pV && pV->getMouseableArea ().pointInside (where))
|
|
{
|
|
if (!options.getIncludeInvisible () && pV->isVisible () == false)
|
|
continue;
|
|
if (options.getMouseEnabled ())
|
|
{
|
|
if (pV->getMouseEnabled() == false)
|
|
continue;
|
|
}
|
|
if (options.getDeep ())
|
|
{
|
|
if (CViewContainer* container = pV->asViewContainer ())
|
|
return container->getContainerAt (where, options);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
return const_cast<CViewContainer*>(this);
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
CPoint& CViewContainer::frameToLocal (CPoint& point) const
|
|
{
|
|
point.offset (-getViewSize ().left, -getViewSize ().top);
|
|
if (auto parent = getParentView ())
|
|
return parent->frameToLocal (point);
|
|
return point;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
CPoint& CViewContainer::localToFrame (CPoint& point) const
|
|
{
|
|
point.offset (getViewSize ().left, getViewSize ().top);
|
|
if (auto parent = getParentView ())
|
|
return parent->localToFrame (point);
|
|
return point;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
bool CViewContainer::removed (CView* parent)
|
|
{
|
|
if (!isAttached ())
|
|
return false;
|
|
|
|
for (const auto& pV : pImpl->children)
|
|
pV->removed (this);
|
|
|
|
return CView::removed (parent);
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
bool CViewContainer::attached (CView* parent)
|
|
{
|
|
if (isAttached ())
|
|
return false;
|
|
|
|
setParentFrame (parent->getFrame ());
|
|
|
|
bool result = CView::attached (parent);
|
|
if (result)
|
|
{
|
|
for (const auto& pV : pImpl->children)
|
|
pV->attached (this);
|
|
}
|
|
if (auto layout = calculateViewLayout (getViewSize ()))
|
|
applyViewLayout (*layout);
|
|
return result;
|
|
}
|
|
|
|
#if DEBUG
|
|
static int32_t _debugDumpLevel = 0;
|
|
//-----------------------------------------------------------------------------
|
|
void CViewContainer::dumpInfo ()
|
|
{
|
|
CView::dumpInfo ();
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
void CViewContainer::dumpHierarchy ()
|
|
{
|
|
_debugDumpLevel++;
|
|
for (auto& pV : pImpl->children)
|
|
{
|
|
for (int32_t i = 0; i < _debugDumpLevel; i++)
|
|
DebugPrint ("\t");
|
|
pV->dumpInfo ();
|
|
DebugPrint ("\n");
|
|
if (CViewContainer* container = pV->asViewContainer ())
|
|
container->dumpHierarchy ();
|
|
}
|
|
_debugDumpLevel--;
|
|
}
|
|
|
|
#endif
|
|
|
|
} // VSTGUI
|