⛏️ index : haiku.git

/*
 * Copyright 2013, Ingo Weinhold, ingo_weinhold@gmx.de.
 * Distributed under the terms of the MIT License.
 */


#include <ViewPort.h>

#include <algorithm>

#include <AbstractLayout.h>
#include <ScrollBar.h>

#include "ViewLayoutItem.h"


namespace BPrivate {


// #pragma mark - ViewPortLayout


class BViewPort::ViewPortLayout : public BAbstractLayout {
public:
	ViewPortLayout(BViewPort* viewPort)
		:
		BAbstractLayout(),
		fViewPort(viewPort),
		fHasViewChild(false),
		fIsCacheValid(false),
		fMin(),
		fMax(),
		fPreferred()
	{
	}

	BView* ChildView() const
	{
		if (!fHasViewChild)
			return NULL;
		if (BViewLayoutItem* item = dynamic_cast<BViewLayoutItem*>(ItemAt(0)))
			return item->View();
		return NULL;
	}

	void SetChildView(BView* view)
	{
		_UnsetChild();

		if (view != NULL && AddView(0, view) != NULL)
			fHasViewChild = true;
	}

	BLayoutItem* ChildItem() const
	{
		return ItemAt(0);
	}

	void SetChildItem(BLayoutItem* item)
	{
		_UnsetChild();

		if (item != NULL)
			AddItem(0, item);
	}

	virtual BSize BaseMinSize()
	{
		_ValidateMinMax();
		return fMin;
	}

	virtual BSize BaseMaxSize()
	{
		_ValidateMinMax();
		return fMax;
	}

	virtual BSize BasePreferredSize()
	{
		_ValidateMinMax();
		return fPreferred;
	}

	virtual BAlignment BaseAlignment()
	{
		return BAbstractLayout::BaseAlignment();
	}

	virtual bool HasHeightForWidth()
	{
		_ValidateMinMax();
		return false;
		// TODO: Support height-for-width!
	}

	virtual void GetHeightForWidth(float width, float* min, float* max,
		float* preferred)
	{
		if (!HasHeightForWidth())
			return;

		// TODO: Support height-for-width!
	}

	virtual void LayoutInvalidated(bool children)
	{
		fIsCacheValid = false;
	}

	virtual void DoLayout()
	{
		_ValidateMinMax();

		BLayoutItem* child = ItemAt(0);
		if (child == NULL)
			return;

		// Determine the layout area: LayoutArea() will only give us the size
		// of the view port's frame.
		BSize viewSize = LayoutArea().Size();
		BSize layoutSize = viewSize;

		BSize childMin = child->MinSize();
		BSize childMax = child->MaxSize();

		// apply the maximum constraints
		layoutSize.width = std::min(layoutSize.width, childMax.width);
		layoutSize.height = std::min(layoutSize.height, childMax.height);

		// apply the minimum constraints
		layoutSize.width = std::max(layoutSize.width, childMin.width);
		layoutSize.height = std::max(layoutSize.height, childMin.height);

		// TODO: Support height-for-width!

		child->AlignInFrame(BRect(BPoint(0, 0), layoutSize));

		_UpdateScrollBar(fViewPort->ScrollBar(B_HORIZONTAL), viewSize.width,
			layoutSize.width);
		_UpdateScrollBar(fViewPort->ScrollBar(B_VERTICAL), viewSize.height,
			layoutSize.height);
	}

private:
	void _UnsetChild()
	{
		if (CountItems() > 0) {
			BLayoutItem* item = RemoveItem((int32)0);
			if (fHasViewChild)
				delete item;
			fHasViewChild = false;
		}
	}

	void _ValidateMinMax()
	{
		if (fIsCacheValid)
			return;

		if (BLayoutItem* child = ItemAt(0)) {
			fMin = child->MinSize();
			if (_IsHorizontallyScrollable())
				fMin.width = -1;
			if (_IsVerticallyScrollable())
				fMin.height = -1;
			fMax = child->MaxSize();
			fPreferred = child->PreferredSize();
			// TODO: Support height-for-width!
		} else {
			fMin.Set(-1, -1);
			fMax.Set(B_SIZE_UNLIMITED, B_SIZE_UNLIMITED);
			fPreferred.Set(20, 20);
		}

		fIsCacheValid = true;
	}

	bool _IsHorizontallyScrollable() const
	{
		return fViewPort->ScrollBar(B_HORIZONTAL) != NULL;
	}

	bool _IsVerticallyScrollable() const
	{
		return fViewPort->ScrollBar(B_VERTICAL) != NULL;
	}

	void _UpdateScrollBar(BScrollBar* scrollBar, float viewPortSize,
		float dataSize)
	{
		if (scrollBar == NULL)
			return;

		if (viewPortSize < dataSize) {
			scrollBar->SetRange(0, dataSize - viewPortSize);
			scrollBar->SetProportion(viewPortSize / dataSize);
			float smallStep;
			scrollBar->GetSteps(&smallStep, NULL);
			scrollBar->SetSteps(smallStep, viewPortSize);
		} else {
			scrollBar->SetRange(0, 0);
			scrollBar->SetProportion(1);
		}
	}

private:
	BViewPort*	fViewPort;
	bool		fHasViewChild;
	bool		fIsCacheValid;
	BSize		fMin;
	BSize		fMax;
	BSize		fPreferred;
};


// #pragma mark - BViewPort


BViewPort::BViewPort(BView* child)
	:
	BView(NULL, 0),
	fChild(NULL)
{
	_Init();
	SetChildView(child);
}


BViewPort::BViewPort(BLayoutItem* child)
	:
	BView(NULL, 0),
	fChild(NULL)
{
	_Init();
	SetChildItem(child);
}


BViewPort::BViewPort(const char* name, BView* child)
	:
	BView(name, 0),
	fChild(NULL)
{
	_Init();
	SetChildView(child);
}


BViewPort::BViewPort(const char* name, BLayoutItem* child)
	:
	BView(name, 0),
	fChild(NULL)
{
	_Init();
	SetChildItem(child);
}


BViewPort::~BViewPort()
{
}


BView*
BViewPort::ChildView() const
{
	return fLayout->ChildView();
}


void
BViewPort::SetChildView(BView* child)
{
	fLayout->SetChildView(child);
	InvalidateLayout();
}


BLayoutItem*
BViewPort::ChildItem() const
{
	return fLayout->ChildItem();
}


void
BViewPort::SetChildItem(BLayoutItem* child)
{
	fLayout->SetChildItem(child);
	InvalidateLayout();
}


void
BViewPort::_Init()
{
	fLayout = new ViewPortLayout(this);
	SetLayout(fLayout);
}


}	// namespace BPrivate


using ::BPrivate::BViewPort;