⛏️ index : haiku.git


#include <stdio.h>

#include <List.h>
#include <View.h> // for resize modes

#include "Desktop.h"
#include "DrawingEngine.h"
#include "WindowLayer.h"

#include "ViewLayer.h"

extern BWindow* wind;

// constructor
ViewLayer::ViewLayer(BRect frame, const char* name,
			 uint32 resizeMode, uint32 flags,
			 rgb_color viewColor)
	: fName(name),

	  fFrame(frame),
	  fScrollingOffset(0.0, 0.0),

	  fViewColor(viewColor),

	  fResizeMode(resizeMode),
	  fFlags(flags),

	  // ViewLayers start visible by default
	  fHidden(false),
	  fVisible(true),

	  fWindow(NULL),
	  fParent(NULL),

	  fFirstChild(NULL),
	  fPreviousSibling(NULL),
	  fNextSibling(NULL),
	  fLastChild(NULL),

	  fCurrentChild(NULL),

	  fLocalClipping(Bounds()),
	  fScreenClipping(),
	  fScreenClippingValid(false)
{
	fFrame.left = float((int32)fFrame.left);
	fFrame.top = float((int32)fFrame.top);
	fFrame.right = float((int32)fFrame.right);
	fFrame.bottom = float((int32)fFrame.bottom);
}

// destructor
ViewLayer::~ViewLayer()
{
	// iterate over children and delete each one
	ViewLayer* layer = fFirstChild;
	while (layer) {
		ViewLayer* toast = layer;
		layer = layer->fNextSibling;
		delete toast;
	}
}

// Bounds
BRect
ViewLayer::Bounds() const
{
	BRect bounds(fScrollingOffset.x, fScrollingOffset.y,
				 fScrollingOffset.x + fFrame.Width(),
				 fScrollingOffset.y + fFrame.Height());
	return bounds;
}

// ConvertToVisibleInTopView
void
ViewLayer::ConvertToVisibleInTopView(BRect* bounds) const
{
	*bounds = *bounds & Bounds();
	// NOTE: this step is necessary even if we don't have a parent!
	ConvertToParent(bounds);

	if (fParent)
		fParent->ConvertToVisibleInTopView(bounds);
}

// AttachedToWindow
void
ViewLayer::AttachedToWindow(WindowLayer* window)
{
	fWindow = window;

	for (ViewLayer* child = FirstChild(); child; child = NextChild())
		child->AttachedToWindow(window);
}


// DetachedFromWindow
void
ViewLayer::DetachedFromWindow()
{
	fWindow = NULL;
	for (ViewLayer* child = FirstChild(); child; child = NextChild())
		child->DetachedFromWindow();
}

// AddChild
void
ViewLayer::AddChild(ViewLayer* layer)
{
	if (layer->fParent) {
		printf("ViewLayer::AddChild() - ViewLayer already has a parent\n");
		return;
	}
	
	layer->fParent = this;
	
	if (!fLastChild) {
		// no children yet
		fFirstChild = layer;
	} else {
		// append layer to formerly last child
		fLastChild->fNextSibling = layer;
		layer->fPreviousSibling = fLastChild;
	}
	fLastChild = layer;

	if (layer->IsVisible())
		RebuildClipping(false);

	if (fWindow) {
		layer->AttachedToWindow(fWindow);

		if (fVisible && layer->IsVisible()) {
			// trigger redraw
			BRect clippedFrame = layer->Frame();
			ConvertToVisibleInTopView(&clippedFrame);
			BRegion dirty(clippedFrame);
			fWindow->MarkContentDirty(&dirty);
		}
	}
}

// RemoveChild
bool
ViewLayer::RemoveChild(ViewLayer* layer)
{
	if (layer->fParent != this) {
		printf("ViewLayer::RemoveChild(%p - %s) - ViewLayer is not child of this (%p) layer!\n", layer, layer ? layer->Name() : NULL, this);
		return false;
	}
	
	layer->fParent = NULL;
	
	if (fLastChild == layer)
		fLastChild = layer->fPreviousSibling;
		// layer->fNextSibling would be NULL
	
	if (fFirstChild == layer )
		fFirstChild = layer->fNextSibling;
		// layer->fPreviousSibling would be NULL

	// connect child before and after layer
	if (layer->fPreviousSibling)
		layer->fPreviousSibling->fNextSibling = layer->fNextSibling;
	
	if (layer->fNextSibling)
		layer->fNextSibling->fPreviousSibling = layer->fPreviousSibling;

	// layer has no siblings anymore
	layer->fPreviousSibling = NULL;
	layer->fNextSibling = NULL;

	if (layer->IsVisible())
		RebuildClipping(false);

	if (fWindow) {
		layer->DetachedFromWindow();

		if (fVisible && layer->IsVisible()) {
			// trigger redraw
			BRect clippedFrame = layer->Frame();
			ConvertToVisibleInTopView(&clippedFrame);
			BRegion dirty(clippedFrame);
			fWindow->MarkContentDirty(&dirty);
		}
	}

	return true;
}

// FirstChild
ViewLayer*
ViewLayer::FirstChild() const
{
	fCurrentChild = fFirstChild;
	return fCurrentChild;
}

// PreviousChild
ViewLayer*
ViewLayer::PreviousChild() const
{
	fCurrentChild = fCurrentChild->fPreviousSibling;
	return fCurrentChild;
}

// NextChild
ViewLayer*
ViewLayer::NextChild() const
{
	fCurrentChild = fCurrentChild->fNextSibling;
	return fCurrentChild;
}

// LastChild
ViewLayer*
ViewLayer::LastChild() const
{
	fCurrentChild = fLastChild;
	return fCurrentChild;
}

// TopLayer
ViewLayer*
ViewLayer::TopLayer()
{
	// returns the top level view of the hirarchy,
	// it doesn't have to be the top level of a window

	if (fParent)
		return fParent->TopLayer();

	return this;
}

// CountChildren
uint32
ViewLayer::CountChildren(bool deep) const
{
	uint32 count = 0;
	for (ViewLayer* child = FirstChild(); child; child = NextChild()) {
		count++;
		if (deep) {
			count += child->CountChildren(deep);
		}
	}
	return count;	
}

// CollectTokensForChildren
void
ViewLayer::CollectTokensForChildren(BList* tokenMap) const
{
	for (ViewLayer* child = FirstChild(); child; child = NextChild()) {
		tokenMap->AddItem((void*)child);
		child->CollectTokensForChildren(tokenMap);
	}
}

// ViewAt
ViewLayer*
ViewLayer::ViewAt(const BPoint& where, BRegion* windowContentClipping)
{
	if (!fVisible)
		return NULL;

	if (ScreenClipping(windowContentClipping).Contains(where))
		return this;

	for (ViewLayer* child = FirstChild(); child; child = NextChild()) {
		ViewLayer* layer = child->ViewAt(where, windowContentClipping);
		if (layer)
			return layer;
	}
	return NULL;
}

// ConvertToParent
void
ViewLayer::ConvertToParent(BPoint* point) const
{
	// remove scrolling offset and convert to parent coordinate space
	point->x += fFrame.left - fScrollingOffset.x;
	point->y += fFrame.top - fScrollingOffset.y;
}

// ConvertToParent
void
ViewLayer::ConvertToParent(BRect* rect) const
{
	// remove scrolling offset and convert to parent coordinate space
	rect->OffsetBy(fFrame.left - fScrollingOffset.x,
				   fFrame.top - fScrollingOffset.y);
}

// ConvertToParent
void
ViewLayer::ConvertToParent(BRegion* region) const
{
	// remove scrolling offset and convert to parent coordinate space
	region->OffsetBy(fFrame.left - fScrollingOffset.x,
					 fFrame.top - fScrollingOffset.y);
}

// ConvertFromParent
void
ViewLayer::ConvertFromParent(BPoint* point) const
{
	// remove scrolling offset and convert to parent coordinate space
	point->x += fScrollingOffset.x - fFrame.left;
	point->y += fScrollingOffset.y - fFrame.top;
}

// ConvertFromParent
void
ViewLayer::ConvertFromParent(BRect* rect) const
{
	// remove scrolling offset and convert to parent coordinate space
	rect->OffsetBy(fScrollingOffset.x - fFrame.left,
				   fScrollingOffset.y - fFrame.top);
}

// ConvertFromParent
void
ViewLayer::ConvertFromParent(BRegion* region) const
{
	// remove scrolling offset and convert to parent coordinate space
	region->OffsetBy(fScrollingOffset.x - fFrame.left,
					 fScrollingOffset.y - fFrame.top);
}

// ConvertToTop
void
ViewLayer::ConvertToTop(BPoint* point) const
{
	ConvertToParent(point);

	if (fParent)
		fParent->ConvertToTop(point);
}

// ConvertToTop
void
ViewLayer::ConvertToTop(BRect* rect) const
{
	ConvertToParent(rect);

	if (fParent)
		fParent->ConvertToTop(rect);
}

// ConvertToTop
void
ViewLayer::ConvertToTop(BRegion* region) const
{
	ConvertToParent(region);

	if (fParent)
		fParent->ConvertToTop(region);
}

// ConvertFromTop
void
ViewLayer::ConvertFromTop(BPoint* point) const
{
	ConvertFromParent(point);

	if (fParent)
		fParent->ConvertFromTop(point);
}

// ConvertFromTop
void
ViewLayer::ConvertFromTop(BRect* rect) const
{
	ConvertFromParent(rect);

	if (fParent)
		fParent->ConvertFromTop(rect);
}

// ConvertFromTop
void
ViewLayer::ConvertFromTop(BRegion* region) const
{
	ConvertFromParent(region);

	if (fParent)
		fParent->ConvertFromTop(region);
}

// SetName
void
ViewLayer::SetName(const char* string)
{
	fName.SetTo(string);
}

// #pragma mark -

// MoveBy
void
ViewLayer::MoveBy(int32 x, int32 y, BRegion* dirtyRegion)
{
	if (x == 0 && y == 0)
		return;

	fFrame.OffsetBy(x, y);

	// to move on screen, we must not be hidden and we must have a parent
	if (fVisible && fParent && dirtyRegion) {
#if 0
// based on redraw on new location
		// the place were we are now visible
		BRect newVisibleBounds = Bounds();
		// we can use the frame of the old
		// local clipping to see which parts need invalidation
		BRect oldVisibleBounds(Bounds());
		oldVisibleBounds.OffsetBy(-x, -y);
		ConvertToTop(&oldVisibleBounds);

		ConvertToVisibleInTopView(&newVisibleBounds);

		dirtyRegion->Include(oldVisibleBounds);
		// newVisibleBounds already is in screen coords
		dirtyRegion->Include(newVisibleBounds);
#else
// blitting version, invalidates
// old contents
		BRect oldVisibleBounds(Bounds());
		oldVisibleBounds.OffsetBy(-x, -y);
		ConvertToTop(&oldVisibleBounds);

		BRect newVisibleBounds(Bounds());
		// NOTE: using ConvertToVisibleInTopView()
		// instead of ConvertToTop()! see below
		ConvertToVisibleInTopView(&newVisibleBounds);

		newVisibleBounds.OffsetBy(-x, -y);

		// clipping oldVisibleBounds to newVisibleBounds
		// makes sure we don't copy parts hidden under
		// parent views
		BRegion copyRegion(oldVisibleBounds & newVisibleBounds);
		fWindow->CopyContents(&copyRegion, x, y);

		BRegion dirty(oldVisibleBounds);
		newVisibleBounds.OffsetBy(x, y);
		dirty.Exclude(newVisibleBounds);
		dirtyRegion->Include(&dirty);
#endif
	}

	if (!fParent) {
		// the top view's screen clipping does not change,
		// because no parts are clipped away from parent
		// views
		_MoveScreenClipping(x, y, true);
	} else {
		// parts might have been revealed from underneath
		// the parent, or might now be hidden underneath
		// the parent, this is taken care of when building
		// the screen clipping
		InvalidateScreenClipping(true);
	}
}

// ResizeBy
void
ViewLayer::ResizeBy(int32 x, int32 y, BRegion* dirtyRegion)
{
	if (x == 0 && y == 0)
		return;

	fFrame.right += x;
	fFrame.bottom += y;

	if (fVisible && dirtyRegion) {
		BRect oldBounds(Bounds());
		oldBounds.right -= x;
		oldBounds.bottom -= y;

		BRegion dirty(Bounds());
		dirty.Include(oldBounds);

		if (!(fFlags & B_FULL_UPDATE_ON_RESIZE)) {
			// the dirty region is just the difference of
			// old and new bounds
			dirty.Exclude(oldBounds & Bounds());
		}

		InvalidateScreenClipping(true);

		if (dirty.CountRects() > 0) {
			// exclude children, they are expected to
			// include their own dirty regions in ParentResized()
			for (ViewLayer* child = FirstChild(); child; child = NextChild()) {
				if (child->IsVisible()) {
					BRect previousChildVisible(child->Frame() & oldBounds & Bounds());
					if (dirty.Frame().Intersects(previousChildVisible)) {
						dirty.Exclude(previousChildVisible);
					}
				}
			}

			ConvertToTop(&dirty);
			dirtyRegion->Include(&dirty);
		}
	}

	// layout the children
	for (ViewLayer* child = FirstChild(); child; child = NextChild())
		child->ParentResized(x, y, dirtyRegion);

	// at this point, children are at their new locations,
	// so we can rebuild the clipping
	// TODO: when the implementation of Hide() and Show() is
	// complete, see if this should be avoided
	RebuildClipping(false);
}

// ParentResized
void
ViewLayer::ParentResized(int32 x, int32 y, BRegion* dirtyRegion)
{
	uint16 rm = fResizeMode & 0x0000FFFF;
	BRect newFrame = fFrame;

	// follow with left side
	if ((rm & 0x0F00U) == _VIEW_RIGHT_ << 8)
		newFrame.left += x;
	else if ((rm & 0x0F00U) == _VIEW_CENTER_ << 8)
		newFrame.left += x / 2;

	// follow with right side
	if ((rm & 0x000FU) == _VIEW_RIGHT_)
		newFrame.right += x;
	else if ((rm & 0x000FU) == _VIEW_CENTER_)
		newFrame.right += x / 2;

	// follow with top side
	if ((rm & 0xF000U) == _VIEW_BOTTOM_ << 12)
		newFrame.top += y;
	else if ((rm & 0xF000U) == _VIEW_CENTER_ << 12)
		newFrame.top += y / 2;

	// follow with bottom side
	if ((rm & 0x00F0U) == _VIEW_BOTTOM_ << 4)
		newFrame.bottom += y;
	else if ((rm & 0x00F0U) == _VIEW_CENTER_ << 4)
		newFrame.bottom += y / 2;

	if (newFrame != fFrame) {
		// careful, MoveBy will change fFrame
		int32 widthDiff = (int32)(newFrame.Width() - fFrame.Width());
		int32 heightDiff = (int32)(newFrame.Height() - fFrame.Height());

		MoveBy(newFrame.left - fFrame.left,
			   newFrame.top - fFrame.top, dirtyRegion);

		ResizeBy(widthDiff, heightDiff, dirtyRegion);
	}
}

// ScrollBy
void
ViewLayer::ScrollBy(int32 x, int32 y, BRegion* dirtyRegion)
{
	// blitting version, invalidates
	// old contents

	// remember old bounds for tracking dirty region
	BRect oldBounds(Bounds());
	// find the area of the view that can be scrolled,
	// contents are shifted in the opposite direction from scrolling
	BRect stillVisibleBounds(oldBounds);
	stillVisibleBounds.OffsetBy(x, y);

	// NOTE: using ConvertToVisibleInTopView()
	// instead of ConvertToTop(), this makes
	// sure we don't try to move or invalidate an
	// area hidden underneath the parent view
	ConvertToVisibleInTopView(&oldBounds);
	ConvertToVisibleInTopView(&stillVisibleBounds);

	fScrollingOffset.x += x;
	fScrollingOffset.y += y;

	// do the blit, this will make sure
	// that other more complex dirty regions
	// are taken care of
	BRegion copyRegion(stillVisibleBounds);
	fWindow->CopyContents(&copyRegion, -x, -y);

	// find the dirty region as far as we are
	// concerned
	BRegion dirty(oldBounds);
	stillVisibleBounds.OffsetBy(-x, -y);
	dirty.Exclude(stillVisibleBounds);
	dirtyRegion->Include(&dirty);

	// the screen clipping of this view and it's
	// childs is no longer valid
	InvalidateScreenClipping(true);
	RebuildClipping(false);
}

// #pragma mark -

// Draw
void
ViewLayer::Draw(DrawingEngine* drawingEngine, BRegion* effectiveClipping,
				BRegion* windowContentClipping, bool deep)
{
	// we can only draw within our own area
	BRegion redraw(ScreenClipping(windowContentClipping));
	// add the current clipping
	redraw.IntersectWith(effectiveClipping);

	// fill visible region with white
	drawingEngine->FillRegion(&redraw, (rgb_color){ 255, 255, 255, 255 });

	// let children draw
	if (deep) {
		// before passing the clipping on to children, exclude our
		// own region from the available clipping
		effectiveClipping->Exclude(&ScreenClipping(windowContentClipping));

		for (ViewLayer* child = FirstChild(); child; child = NextChild()) {
			child->Draw(drawingEngine, effectiveClipping,
						windowContentClipping, deep);
		}
	}
}

// ClientDraw
void
ViewLayer::ClientDraw(DrawingEngine* drawingEngine, BRegion* effectiveClipping)
{
	BRect b(Bounds());
	b.OffsetTo(0.0, 0.0);
	ConvertToTop(&b);

	if (drawingEngine->Lock()) {
		drawingEngine->ConstrainClipping(effectiveClipping);

		// draw a frame with the view color
		drawingEngine->StrokeRect(b, fViewColor);
		b.InsetBy(1, 1);
		drawingEngine->StrokeRect(b, fViewColor);
		b.InsetBy(1, 1);
		drawingEngine->StrokeRect(b, fViewColor);
		b.InsetBy(1, 1);
		drawingEngine->StrokeLine(b.LeftTop(), b.RightBottom(), fViewColor);

		drawingEngine->Unlock();
	}
}

// #pragma mark -

// SetHidden
void
ViewLayer::SetHidden(bool hidden)
{
	// TODO: test...

	if (fHidden != hidden) {
		fHidden = hidden;

		// recurse into children and update their visible flag
		if (fParent) {
			bool olfVisible = fVisible;
			UpdateVisibleDeep(fParent->IsVisible());
			if (olfVisible != fVisible) {
				// include or exclude us from the parent area
				fParent->RebuildClipping(false);
				if (fWindow) {
					// trigger a redraw
					BRect clippedBounds = Bounds();
					ConvertToVisibleInTopView(&clippedBounds);
					BRegion dirty(clippedBounds);
					fWindow->MarkContentDirty(&dirty);
				}
			}
		} else {
			UpdateVisibleDeep(true);
		}
	}
}

// IsHidden
bool
ViewLayer::IsHidden() const
{
	return fHidden;
}

// UpdateVisibleDeep
void
ViewLayer::UpdateVisibleDeep(bool parentVisible)
{
	fVisible = parentVisible && !fHidden;
	for (ViewLayer* child = FirstChild(); child; child = NextChild())
		child->UpdateVisibleDeep(fVisible);
}

// PrintToStream
void
ViewLayer::PrintToStream() const
{
}

// RebuildClipping
void
ViewLayer::RebuildClipping(bool deep)
{
	// the clipping spans over the bounds area
	fLocalClipping.Set(Bounds());

	// exclude all childs from the clipping
	for (ViewLayer* child = FirstChild(); child; child = NextChild()) {
		if (child->IsVisible())
			fLocalClipping.Exclude(child->Frame());

		if (deep)
			child->RebuildClipping(deep);
	}

	fScreenClippingValid = false;
}

// ScreenClipping
BRegion&
ViewLayer::ScreenClipping(BRegion* windowContentClipping, bool force) const
{
	if (!fScreenClippingValid || force) {
		fScreenClipping = fLocalClipping;
		ConvertToTop(&fScreenClipping);

		// see if we parts of our bounds are hidden underneath
		// the parent, the local clipping does not account for this
		BRect clippedBounds = Bounds();
		ConvertToVisibleInTopView(&clippedBounds);
		if (clippedBounds.Width() < fScreenClipping.Frame().Width() ||
			clippedBounds.Height() < fScreenClipping.Frame().Height()) {
			BRegion temp(clippedBounds);
			fScreenClipping.IntersectWith(&temp);
		}

		fScreenClipping.IntersectWith(windowContentClipping);
		fScreenClippingValid = true;
	}
	return fScreenClipping;
}

// InvalidateScreenClipping
void
ViewLayer::InvalidateScreenClipping(bool deep)
{
	fScreenClippingValid = false;
	if (deep) {
		// invalidate the childrens screen clipping as well
		for (ViewLayer* child = FirstChild(); child; child = NextChild()) {
			child->InvalidateScreenClipping(deep);
		}
	}
}

// _MoveScreenClipping
void
ViewLayer::_MoveScreenClipping(int32 x, int32 y, bool deep)
{
	if (fScreenClippingValid)
		fScreenClipping.OffsetBy(x, y);

	if (deep) {
		// move the childrens screen clipping as well
		for (ViewLayer* child = FirstChild(); child; child = NextChild()) {
			child->_MoveScreenClipping(x, y, deep);
		}
	}
}