⛏️ index : haiku.git


#include <new>
#include <stdio.h>

#include <Message.h>
#include <MessageQueue.h>

#include "ClientLooper.h"
#include "Desktop.h"
#include "DrawingEngine.h"

#include "WindowLayer.h"

// if the background clearing is delayed until
// the client draws the view, we have less flickering
// when contents have to be redrawn because of resizing
// a window or because the client invalidates parts.
// when redrawing something that has been exposed from underneath
// other windows, the other window will be seen longer at
// its previous position though if the exposed parts are not
// cleared right away. maybe there ought to be a flag in
// the update session, which tells us the cause of the update
#define DELAYED_BACKGROUND_CLEARING 1

// IMPORTANT: nested ReadLockClipping()s are not supported (by MultiLocker)


// constructor
WindowLayer::WindowLayer(BRect frame, const char* name,
						 DrawingEngine* drawingEngine, Desktop* desktop)
	: BLooper(name, B_DISPLAY_PRIORITY),
	  fFrame(frame),

	  fVisibleRegion(),
	  fVisibleContentRegion(),
	  fVisibleContentRegionValid(false),
	  fDirtyRegion(),

	  fBorderRegion(),
	  fBorderRegionValid(false),
	  fContentRegion(),
	  fContentRegionValid(false),
	  fEffectiveDrawingRegion(),
	  fEffectiveDrawingRegionValid(false),

	  fFocus(false),

	  fTopLayer(NULL),

// TODO: windows must start hidden!
	  fHidden(false),
	  // windows start hidden
//	  fHidden(true),

	  fDrawingEngine(drawingEngine),
	  fDesktop(desktop),

	  fTokenViewMap(64),

	  fClient(new ClientLooper(name, this)),
	  fCurrentUpdateSession(),
	  fPendingUpdateSession(),
	  fUpdateRequested(false),
	  fInUpdate(false)
{
	// the top layer is special, it has a coordinate system
	// as if it was attached directly to the desktop, therefor,
	// the coordinate conversion through the layer tree works
	// as expected, since the top layer has no "parent" but has
	// fFrame as if it had
	fTopLayer = new(nothrow) ViewLayer(fFrame, "top view", B_FOLLOW_ALL, 0,
									   (rgb_color){ 255, 255, 255, 255 });
	fTopLayer->AttachedToWindow(this);

	fClient->Run();
}

// destructor
WindowLayer::~WindowLayer()
{
	delete fTopLayer;
}

// MessageReceived
void
WindowLayer::MessageReceived(BMessage* message)
{
	switch (message->what) {
		case MSG_REDRAW: {
			// there is only one MSG_REDRAW in the queue at anytime
			if (fDesktop->ReadLockClipping()) {

				_DrawBorder();
				_TriggerContentRedraw();

				// reset the dirty region, since
				// we're fully clean. If the desktop
				// thread wanted to mark something
				// dirty in the mean time, it was
				// blocking on the global region lock to
				// get write access, since we held the
				// read lock for the whole time.
				fDirtyRegion.MakeEmpty();

				fDesktop->ReadUnlockClipping();
			} else {
//printf("%s MSG_REDRAW -> pending redraws\n", Name());
			}
			break;
		}
		case MSG_BEGIN_UPDATE:
			_BeginUpdate();
			break;
		case MSG_END_UPDATE:
			_EndUpdate();
			break;
		case MSG_DRAWING_COMMAND: {
			int32 token;
			if (message->FindInt32("token", &token) >= B_OK)
				_DrawClient(token);
			break;
		}
		case MSG_DRAW_POLYGON: {
			int32 token;
			BPoint polygon[4];
			if (message->FindInt32("token", &token) >= B_OK &&
				message->FindPoint("point", 0, &polygon[0]) >= B_OK &&
				message->FindPoint("point", 1, &polygon[1]) >= B_OK &&
				message->FindPoint("point", 2, &polygon[2]) >= B_OK &&
				message->FindPoint("point", 3, &polygon[3]) >= B_OK) {

				_DrawClientPolygon(token, polygon);
			}
			break;
			
		}

		case MSG_INVALIDATE_VIEW: {
			int32 token;
			if (message->FindInt32("token", &token) >= B_OK)
				InvalidateView(token);
			break;
		}

		case MSG_SHOW:
			if (IsHidden()) {
				fDesktop->ShowWindow(this);
			}
			break;
		default:
			BLooper::MessageReceived(message);
			break;
	}
}

// QuitRequested
bool
WindowLayer::QuitRequested()
{
	if (fDesktop && fDesktop->LockClipping()) {
		fDesktop->WindowDied(this);

		fClient->Lock();
		fClient->Quit();

		fDesktop->UnlockClipping();
	}
	return true;
}

// SetClipping
void
WindowLayer::SetClipping(BRegion* stillAvailableOnScreen)
{
	// this function is only called from the Desktop thread

	// start from full region (as if the window was fully visible)
	GetFullRegion(&fVisibleRegion);
	// clip to region still available on screen
	fVisibleRegion.IntersectWith(stillAvailableOnScreen);

	fVisibleContentRegionValid = false;
	fEffectiveDrawingRegionValid = false;
}

// GetFullRegion
void
WindowLayer::GetFullRegion(BRegion* region) const
{
	// start from the frame, extend to include decorator border
	region->Set(BRect(fFrame.left - 4, fFrame.top - 4,
					  fFrame.right + 4, fFrame.bottom + 4));
	// add the title tab
	region->Include(BRect(fFrame.left - 4, fFrame.top - 20,
						  ceilf((fFrame.left + fFrame.right) / 2), fFrame.top - 5));
}

// GetBorderRegion
void
WindowLayer::GetBorderRegion(BRegion* region)
{
	if (!fBorderRegionValid) {
		fBorderRegion.Set(BRect(fFrame.left - 4, fFrame.top - 20,
							  	ceilf((fFrame.left + fFrame.right) / 2), fFrame.top - 5));
		fBorderRegion.Include(BRect(fFrame.left - 4, fFrame.top - 4,
									fFrame.right + 4, fFrame.top - 1));
		fBorderRegion.Include(BRect(fFrame.left - 4, fFrame.top,
									fFrame.left - 1, fFrame.bottom));
		fBorderRegion.Include(BRect(fFrame.right + 1, fFrame.top,
									fFrame.right + 4, fFrame.bottom - 11));
		fBorderRegion.Include(BRect(fFrame.left - 4, fFrame.bottom + 1,
									fFrame.right - 11, fFrame.bottom + 4));
		fBorderRegion.Include(BRect(fFrame.right - 10, fFrame.bottom - 10,
									fFrame.right + 4, fFrame.bottom + 4));
		fBorderRegionValid = true;
	}

	*region = fBorderRegion;
}

// GetContentRegion
void
WindowLayer::GetContentRegion(BRegion* region)
{
	if (!fContentRegionValid) {
		_UpdateContentRegion();
	}

	*region = fContentRegion;
}

// VisibleContentRegion
BRegion&
WindowLayer::VisibleContentRegion()
{
	// regions expected to be locked
	if (!fVisibleContentRegionValid) {
		GetContentRegion(&fVisibleContentRegion);
		fVisibleContentRegion.IntersectWith(&fVisibleRegion);
	}
	return fVisibleContentRegion;
}

// SetFocus
void
WindowLayer::SetFocus(bool focus)
{
	// executed from Desktop thread
	// it holds the clipping write lock,
	// so the window thread cannot be
	// accessing fFocus

	BRegion dirty(fBorderRegion);
	dirty.IntersectWith(&fVisibleRegion);
	fDesktop->MarkDirty(&dirty);

	fFocus = focus;
}

// MoveBy
void
WindowLayer::MoveBy(int32 x, int32 y)
{
	// this function is only called from the desktop thread

	if (x == 0 && y == 0)
		return;

	fFrame.OffsetBy(x, y);

	// take along the dirty region which have not
	// processed yet
	fDirtyRegion.OffsetBy(x, y);

	if (fBorderRegionValid)
		fBorderRegion.OffsetBy(x, y);
	if (fContentRegionValid)
		fContentRegion.OffsetBy(x, y);

	if (fCurrentUpdateSession.IsUsed())
		fCurrentUpdateSession.MoveBy(x, y);
	if (fPendingUpdateSession.IsUsed())
		fPendingUpdateSession.MoveBy(x, y);

	fEffectiveDrawingRegionValid = false;

	fTopLayer->MoveBy(x, y, NULL);

	// the desktop will take care of dirty regions
}

// ResizeBy
void
WindowLayer::ResizeBy(int32 x, int32 y, BRegion* dirtyRegion)
{
	// this function is only called from the desktop thread

	if (x == 0 && y == 0)
		return;

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

	// put the previous border region into the dirty region as well
	// to handle the part that was overlapping a layer
	dirtyRegion->Include(&fBorderRegion);

	fBorderRegionValid = false;
	fContentRegionValid = false;
	fEffectiveDrawingRegionValid = false;

	// the border is dirty, put it into
	// dirtyRegion for a start
	BRegion newBorderRegion;
	GetBorderRegion(&newBorderRegion);
	dirtyRegion->Include(&newBorderRegion);

	fTopLayer->ResizeBy(x, y, dirtyRegion);
}

// ScrollViewBy
void
WindowLayer::ScrollViewBy(ViewLayer* view, int32 dx, int32 dy)
{
	// this can be executed from any thread, but if the
	// desktop thread is executing this, it should have
	// the write lock, otherwise it is not prevented
	// from executing this at the same time as the window
	// is doing something else here!

	if (!view || view == fTopLayer || (dx == 0 && dy == 0))
		return;

	if (fDesktop && fDesktop->ReadLockClipping()) {

		BRegion dirty;
		view->ScrollBy(dx, dy, &dirty);

		_MarkContentDirty(&dirty);

		fDesktop->ReadUnlockClipping();
	}
}

// AddChild
void
WindowLayer::AddChild(ViewLayer* layer)
{
	fTopLayer->AddChild(layer);

	// inform client about the view
	// (just a part of the simulation)
	fTokenViewMap.MakeEmpty();
	fTopLayer->CollectTokensForChildren(&fTokenViewMap);
	BMessage message(MSG_VIEWS_ADDED);
	message.AddInt32("count", fTokenViewMap.CountItems());
	fClient->PostMessage(&message);

	// TODO: trigger redraw for dirty regions
}

// ViewAt
ViewLayer*
WindowLayer::ViewAt(const BPoint& where)
{
	if (!fContentRegionValid)
		_UpdateContentRegion();

	return fTopLayer->ViewAt(where, &fContentRegion);
}

// SetHidden
void
WindowLayer::SetHidden(bool hidden)
{
	// the desktop takes care of dirty regions
	if (fHidden != hidden) {
		fHidden = hidden;

		fTopLayer->SetHidden(hidden);

		// this is only for simulation purposes:
		if (fHidden)
			fClient->PostMessage(MSG_WINDOW_HIDDEN);

		// TODO: anything else?
	}
}

// ProcessDirtyRegion
void
WindowLayer::ProcessDirtyRegion(BRegion* region)
{
	// if this is exectuted in the desktop thread,
	// it means that the window thread currently
	// blocks to get the read lock, if it is
	// executed from the window thread, it should
	// have the read lock and the desktop thread
	// is blocking to get the write lock. IAW, this
	// is only executed in one thread.
	if (fDirtyRegion.CountRects() == 0) {
		// the window needs to be informed
		// when the dirty region was empty.
		// NOTE: when the window thread has processed
		// the dirty region in MessageReceived(),
		// it will make the region empty again, 
		// when it is empty here, we need to send
		// the message to initiate the next update round.
		// Until the message is processed in the window
		// thread, the desktop thread can add parts to
		// the region as it likes.
		PostMessage(MSG_REDRAW, this);
	}
	// this is executed from the desktop thread
	fDirtyRegion.Include(region);
}

// MarkDirty
void
WindowLayer::MarkDirty(BRegion* regionOnScreen)
{
	// for marking any part of the desktop dirty
	// this will get write access to the global
	// region lock, and result in ProcessDirtyRegion()
	// to be called for any windows affected
	if (fDesktop)
		fDesktop->MarkDirty(regionOnScreen);
}

// MarkContentDirty
void
WindowLayer::MarkContentDirty(BRegion* regionOnScreen)
{
	// for triggering MSG_REDRAW
	// since this won't affect other windows, read locking
	// is sufficient. If there was no dirty region before,
	// an update message is triggered
	if (fDesktop && fDesktop->ReadLockClipping()) {

		regionOnScreen->IntersectWith(&VisibleContentRegion());
		ProcessDirtyRegion(regionOnScreen);

		fDesktop->ReadUnlockClipping();
	}
}

// InvalidateView
void
WindowLayer::InvalidateView(int32 token)
{
	if (fDesktop && fDesktop->ReadLockClipping()) {

		ViewLayer* layer = (ViewLayer*)fTokenViewMap.ItemAt(token);
		if (!layer || !layer->IsVisible()) {
			fDesktop->ReadUnlockClipping();
			return;
		}
		if (!fContentRegionValid)
			_UpdateContentRegion();

		_MarkContentDirty(&layer->ScreenClipping(&fContentRegion));

		fDesktop->ReadUnlockClipping();
	}
}

//# pragma mark -

// CopyContents
void
WindowLayer::CopyContents(BRegion* region, int32 xOffset, int32 yOffset)
{
	// this function takes care of invalidating parts that could not be copied

	if (fDesktop->ReadLockClipping()) {

		BRegion newDirty(*region);

		// clip the region to the visible contents at the
		// source and destination location (not that VisibleContentRegion()
		// is used once to make sure it is valid, then fVisibleContentRegion
		// is used directly)
		region->IntersectWith(&VisibleContentRegion());
		if (region->CountRects() > 0) {
			region->OffsetBy(xOffset, yOffset);
			region->IntersectWith(&fVisibleContentRegion);
			if (region->CountRects() > 0) {
				// if the region still contains any rects
				// offset to source location again
				region->OffsetBy(-xOffset, -yOffset);
				// the part which we can copy is not dirty
				newDirty.Exclude(region);
		
				if (fDrawingEngine->Lock()) {
					fDrawingEngine->CopyRegion(region, xOffset, yOffset);
					fDrawingEngine->Unlock();
				}

				// move along the already dirty regions that are common
				// with the region that we could copy
				_ShiftPartOfRegion(&fDirtyRegion, region, xOffset, yOffset);
				if (fCurrentUpdateSession.IsUsed())
					_ShiftPartOfRegion(&fCurrentUpdateSession.DirtyRegion(), region, xOffset, yOffset);
				if (fPendingUpdateSession.IsUsed())
					_ShiftPartOfRegion(&fPendingUpdateSession.DirtyRegion(), region, xOffset, yOffset);
		
			}
		}
		// what is left visible from the original region
		// at the destination after the region which could be
		// copied has been excluded, is considered dirty
		// NOTE: it may look like dirty regions are not moved
		// if no region could be copied, but that's alright,
		// since these parts will now be in newDirty anyways
		// (with the right offset)
		newDirty.OffsetBy(xOffset, yOffset);
		newDirty.IntersectWith(&fVisibleContentRegion);
		if (newDirty.CountRects() > 0)
			ProcessDirtyRegion(&newDirty);

		fDesktop->ReadUnlockClipping();
	}
}

// #pragma mark -

// _ShiftPartOfRegion
void
WindowLayer::_ShiftPartOfRegion(BRegion* region, BRegion* regionToShift,
								int32 xOffset, int32 yOffset)
{
	BRegion common(*regionToShift);
	// see if there is a common part at all
	common.IntersectWith(region);
	if (common.CountRects() > 0) {
		// cut the common part from the region,
		// offset that to destination and include again
		region->Exclude(&common);
		common.OffsetBy(xOffset, yOffset);
		region->Include(&common);
	}
}

// _TriggerContentRedraw
void
WindowLayer::_TriggerContentRedraw()
{
//printf("%s - DrawContents()\n", Name());
	BRegion dirtyContentRegion(VisibleContentRegion());
	dirtyContentRegion.IntersectWith(&fDirtyRegion);

	if (dirtyContentRegion.CountRects() > 0) {

#if SHOW_WINDOW_CONTENT_DIRTY_REGION
if (fDrawingEngine->Lock()) {
fDrawingEngine->SetHighColor(0, 0, 255);
fDrawingEngine->FillRegion(&dirtyContentRegion);
fDrawingEngine->MarkDirty(&dirtyContentRegion);
fDrawingEngine->Unlock();
snooze(100000);
}
#endif
		// send UPDATE message to the client
		_MarkContentDirty(&dirtyContentRegion);

		if (!fContentRegionValid)
			_UpdateContentRegion();

#if DELAYED_BACKGROUND_CLEARING
		fTopLayer->Draw(fDrawingEngine, &dirtyContentRegion,
						&fContentRegion, false);
#else
		fTopLayer->Draw(fDrawingEngine, &dirtyContentRegion,
						&fContentRegion, true);
#endif
	}
}

// _DrawClient
void
WindowLayer::_DrawClient(int32 token)
{
	// This function is only executed in the window thread.
	// It still needs to block on the clipping lock, since
	// We have to be sure that the clipping is up to date.
	// If true readlocking would work correctly, this would
	// not be an issue
	if (fDesktop->ReadLockClipping()) {

		ViewLayer* layer = (ViewLayer*)fTokenViewMap.ItemAt(token);
		if (!layer || !layer->IsVisible()) {
			fDesktop->ReadUnlockClipping();
			return;
		}

		if (!fEffectiveDrawingRegionValid) {
			fEffectiveDrawingRegion = VisibleContentRegion();
			if (fInUpdate) {
				// enforce the dirty region of the update session
				fEffectiveDrawingRegion.IntersectWith(&fCurrentUpdateSession.DirtyRegion());
			} else {
				printf("%s - _DrawClient(token: %ld) - not in update\n", Name(), token);
			}
			fEffectiveDrawingRegionValid = true;
		}

		// TODO: this is a region that needs to be cached later in the server
		// when the current layer in ServerWindow is set, and we are currently
		// in an update (fInUpdate), than we can set this region and remember
		// it for the comming drawing commands until the current layer changes
		// again or fEffectiveDrawingRegionValid is suddenly false.
		BRegion effectiveClipping(fEffectiveDrawingRegion);
		if (!fContentRegionValid)
			_UpdateContentRegion();
		effectiveClipping.IntersectWith(&layer->ScreenClipping(&fContentRegion));

		if (effectiveClipping.CountRects() > 0) {
#if DELAYED_BACKGROUND_CLEARING
			// clear the back ground
			// TODO: only if this is the first drawing command for
			// this layer of course! in the simulation, all client
			// drawing is done from a single command yet
			layer->Draw(fDrawingEngine, &effectiveClipping,
						&fContentRegion, false);
#endif

			layer->ClientDraw(fDrawingEngine, &effectiveClipping);
		}

		fDesktop->ReadUnlockClipping();
	}
}

// _DrawClientPolygon
void
WindowLayer::_DrawClientPolygon(int32 token, BPoint polygon[4])
{
	if (fDesktop->ReadLockClipping()) {

		ViewLayer* layer = (ViewLayer*)fTokenViewMap.ItemAt(token);
		if (!layer || !layer->IsVisible()) {
			fDesktop->ReadUnlockClipping();
			return;
		}

		if (!fEffectiveDrawingRegionValid) {
			fEffectiveDrawingRegion = VisibleContentRegion();
			if (fInUpdate) {
				// enforce the dirty region of the update session
				fEffectiveDrawingRegion.IntersectWith(&fCurrentUpdateSession.DirtyRegion());
			} else {
				printf("%s - _DrawClientPolygon(token: %ld) - not in update\n", Name(), token);
			}
			fEffectiveDrawingRegionValid = true;
		}

		BRegion effectiveClipping(fEffectiveDrawingRegion);
		if (!fContentRegionValid)
			_UpdateContentRegion();
		effectiveClipping.IntersectWith(&layer->ScreenClipping(&fContentRegion));

		if (effectiveClipping.CountRects() > 0) {
#if DELAYED_BACKGROUND_CLEARING
			layer->Draw(fDrawingEngine, &effectiveClipping,
						&fContentRegion, false);
#endif

			layer->ConvertToTop(&polygon[0]);
			layer->ConvertToTop(&polygon[1]);
			layer->ConvertToTop(&polygon[2]);
			layer->ConvertToTop(&polygon[3]);

			if (fDrawingEngine->Lock()) {

				fDrawingEngine->ConstrainClipping(&effectiveClipping);

//				fDrawingEngine->SetPenSize(3);
//				fDrawingEngine->SetDrawingMode(B_OP_BLEND);
				fDrawingEngine->StrokeLine(polygon[0], polygon[1], layer->ViewColor());
				fDrawingEngine->StrokeLine(polygon[1], polygon[2], layer->ViewColor());
				fDrawingEngine->StrokeLine(polygon[2], polygon[3], layer->ViewColor());
				fDrawingEngine->StrokeLine(polygon[3], polygon[0], layer->ViewColor());

				fDrawingEngine->Unlock();
			}
		}

		fDesktop->ReadUnlockClipping();
	}
}


// _DrawBorder
void
WindowLayer::_DrawBorder()
{
	// this is executed in the window thread, but only
	// in respond to MSG_REDRAW having been received, the
	// clipping lock is held for reading

	// construct the region of the border that needs redrawing
	BRegion dirtyBorderRegion;
	GetBorderRegion(&dirtyBorderRegion);
// TODO: why is it not enough to only intersect with the dirty region?
// is it faster to intersect the dirty region with the visible when it
// is set in ProcessDirtyRegion()?
	// intersect with our visible region
	dirtyBorderRegion.IntersectWith(&fVisibleRegion);
	// intersect with the dirty region
	dirtyBorderRegion.IntersectWith(&fDirtyRegion);

	if (dirtyBorderRegion.CountRects() > 0) {

		rgb_color lowColor;
		rgb_color highColor;
		if (fFocus) {
			lowColor = (rgb_color){ 255, 203, 0, 255 };
			highColor = (rgb_color){ 0, 0, 0, 255 };
		} else {
			lowColor = (rgb_color){ 216, 216, 216, 0 };
			highColor = (rgb_color){ 30, 30, 30, 255 };
		}

		fDrawingEngine->FillRegion(&dirtyBorderRegion, lowColor);

		rgb_color light = tint_color(lowColor, B_LIGHTEN_2_TINT);
		rgb_color shadow = tint_color(lowColor, B_DARKEN_2_TINT);

		if (fDrawingEngine->Lock()) {

			fDrawingEngine->ConstrainClipping(&dirtyBorderRegion);

			fDrawingEngine->DrawString(Name(), BPoint(fFrame.left, fFrame.top - 5), highColor);

			BRect frame(fFrame);
			frame.InsetBy(-1, -1);
			fDrawingEngine->StrokeLine(BPoint(frame.left, frame.bottom),
									   BPoint(frame.left, frame.top), shadow);
			fDrawingEngine->StrokeLine(BPoint(frame.left + 1, frame.top),
									   BPoint(frame.right, frame.top), shadow);
			fDrawingEngine->StrokeLine(BPoint(frame.right, frame.top + 1),
									   BPoint(frame.right, frame.bottom - 11), light);
			fDrawingEngine->StrokeLine(BPoint(frame.right - 1, frame.bottom - 11),
									   BPoint(frame.right - 11, frame.bottom - 11), light);
			fDrawingEngine->StrokeLine(BPoint(frame.right - 11, frame.bottom - 10),
									   BPoint(frame.right - 11, frame.bottom), light);
			fDrawingEngine->StrokeLine(BPoint(frame.right - 12, frame.bottom),
									   BPoint(frame.left + 1, frame.bottom), light);

			frame.InsetBy(-3, -3);
			int32 tabRight = ceilf((fFrame.left + fFrame.right) / 2);
			fDrawingEngine->StrokeLine(BPoint(frame.left, frame.bottom),
									   BPoint(frame.left, frame.top - 16), light);
			fDrawingEngine->StrokeLine(BPoint(frame.left + 1, frame.top - 16),
									   BPoint(tabRight, frame.top - 16), light);
			fDrawingEngine->StrokeLine(BPoint(tabRight, frame.top - 15),
									   BPoint(tabRight, frame.top), shadow);
			fDrawingEngine->StrokeLine(BPoint(tabRight + 1, frame.top),
									   BPoint(frame.right, frame.top), light);
			fDrawingEngine->StrokeLine(BPoint(frame.right, frame.top + 1),
									   BPoint(frame.right, frame.bottom), shadow);
			fDrawingEngine->StrokeLine(BPoint(frame.right, frame.bottom),
									   BPoint(frame.left + 1, frame.bottom), shadow);

			fDrawingEngine->ConstrainClipping(NULL);
			fDrawingEngine->Unlock();
		}
	}
}

// _MarkContentDirty
//
// pre: the clipping is readlocked (this function is
// only called from _TriggerContentRedraw()), which
// in turn is only called from MessageReceived() with
// the clipping lock held
void
WindowLayer::_MarkContentDirty(BRegion* contentDirtyRegion)
{
	if (contentDirtyRegion->CountRects() <= 0)
		return;

	// add to pending
	fPendingUpdateSession.SetUsed(true);
	fPendingUpdateSession.Include(contentDirtyRegion);

	// clip pending update session from current
	// update session, it makes no sense to draw stuff
	// already needing a redraw anyways. Theoretically,
	// this could be done smarter (clip layers from pending
	// that have not yet been redrawn in the current update
	// session)
	if (fCurrentUpdateSession.IsUsed()) {
		fCurrentUpdateSession.Exclude(contentDirtyRegion);
		fEffectiveDrawingRegionValid = false;
	}

	if (!fUpdateRequested) {
		// send this to client
		fClient->PostMessage(MSG_UPDATE);
		fUpdateRequested = true;
		// as long as we have not received
		// the "begin update" command, the
		// pending session does not become the
		// current
	}
}

// _BeginUpdate
void
WindowLayer::_BeginUpdate()
{
	// TODO: since we might "shift" parts of the
	// internal dirty regions from the desktop thread
	// in respond to WindowLayer::ResizeBy(), which
	// might move arround views, this function needs to block
	// on the global clipping lock so that the internal
	// dirty regions are not messed with from both threads
	// at the same time.
	if (fDesktop->ReadLockClipping()) {

		if (fUpdateRequested && !fCurrentUpdateSession.IsUsed()) {
			if (fPendingUpdateSession.IsUsed()) {

				// TODO: the toggling between the update sessions is too
				// expensive, optimize with some pointer tricks
				fCurrentUpdateSession = fPendingUpdateSession;
				fPendingUpdateSession.SetUsed(false);
		
				// all drawing command from the client
				// will have the dirty region from the update
				// session enforced
				fInUpdate = true;
			}
			fEffectiveDrawingRegionValid = false;
		}

		fDesktop->ReadUnlockClipping();
	}
}

// _EndUpdate
void
WindowLayer::_EndUpdate()
{
	// TODO: see comment in _BeginUpdate()
	if (fDesktop->ReadLockClipping()) {
	
		if (fInUpdate) {
			fCurrentUpdateSession.SetUsed(false);
		
			fInUpdate = false;
			fEffectiveDrawingRegionValid = false;
		}
		if (fPendingUpdateSession.IsUsed()) {
			// send this to client
			fClient->PostMessage(MSG_UPDATE);
			fUpdateRequested = true;
		} else {
			fUpdateRequested = false;
		}
	
	fDesktop->ReadUnlockClipping();
	}
}

// _UpdateContentRegion
void
WindowLayer::_UpdateContentRegion()
{
	// TODO: speed up by avoiding "Exclude()"
	// start from the frame, extend to include decorator border
	fContentRegion.Set(fFrame);

	// resize handle
	// if (B_DOCUMENT_WINDOW_LOOK)
		fContentRegion.Exclude(BRect(fFrame.right - 10, fFrame.bottom - 10,
									 fFrame.right, fFrame.bottom));

	fContentRegionValid = true;
}

// #pragma mark -

// constructor
UpdateSession::UpdateSession()
	: fDirtyRegion(),
	  fInUse(false)
{
}

// destructor
UpdateSession::~UpdateSession()
{
}

// Include
void
UpdateSession::Include(BRegion* additionalDirty)
{
	fDirtyRegion.Include(additionalDirty);
}

// Exclude
void
UpdateSession::Exclude(BRegion* dirtyInNextSession)
{
	fDirtyRegion.Exclude(dirtyInNextSession);
}

// MoveBy
void
UpdateSession::MoveBy(int32 x, int32 y)
{
	fDirtyRegion.OffsetBy(x, y);
}

// SetUsed
void
UpdateSession::SetUsed(bool used)
{
	fInUse = used;
	if (!fInUse) {
		fDirtyRegion.MakeEmpty();
	}
}

// operator=
UpdateSession&
UpdateSession::operator=(const UpdateSession& other)
{
	fDirtyRegion = other.fDirtyRegion;
	fInUse = other.fInUse;
	return *this;
}