#include <new>
#include <stdio.h>
#include <Message.h>
#include <MessageQueue.h>
#include "ClientLooper.h"
#include "Desktop.h"
#include "DrawingEngine.h"
#include "WindowLayer.h"
#define DELAYED_BACKGROUND_CLEARING 1
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),
fHidden(false),
fDrawingEngine(drawingEngine),
fDesktop(desktop),
fTokenViewMap(64),
fClient(new ClientLooper(name, this)),
fCurrentUpdateSession(),
fPendingUpdateSession(),
fUpdateRequested(false),
fInUpdate(false)
{
fTopLayer = new(nothrow) ViewLayer(fFrame, "top view", B_FOLLOW_ALL, 0,
(rgb_color){ 255, 255, 255, 255 });
fTopLayer->AttachedToWindow(this);
fClient->Run();
}
WindowLayer::~WindowLayer()
{
delete fTopLayer;
}
void
WindowLayer::MessageReceived(BMessage* message)
{
switch (message->what) {
case MSG_REDRAW: {
if (fDesktop->ReadLockClipping()) {
_DrawBorder();
_TriggerContentRedraw();
fDirtyRegion.MakeEmpty();
fDesktop->ReadUnlockClipping();
} else {
}
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;
}
}
bool
WindowLayer::QuitRequested()
{
if (fDesktop && fDesktop->LockClipping()) {
fDesktop->WindowDied(this);
fClient->Lock();
fClient->Quit();
fDesktop->UnlockClipping();
}
return true;
}
void
WindowLayer::SetClipping(BRegion* stillAvailableOnScreen)
{
GetFullRegion(&fVisibleRegion);
fVisibleRegion.IntersectWith(stillAvailableOnScreen);
fVisibleContentRegionValid = false;
fEffectiveDrawingRegionValid = false;
}
void
WindowLayer::GetFullRegion(BRegion* region) const
{
region->Set(BRect(fFrame.left - 4, fFrame.top - 4,
fFrame.right + 4, fFrame.bottom + 4));
region->Include(BRect(fFrame.left - 4, fFrame.top - 20,
ceilf((fFrame.left + fFrame.right) / 2), fFrame.top - 5));
}
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;
}
void
WindowLayer::GetContentRegion(BRegion* region)
{
if (!fContentRegionValid) {
_UpdateContentRegion();
}
*region = fContentRegion;
}
BRegion&
WindowLayer::VisibleContentRegion()
{
if (!fVisibleContentRegionValid) {
GetContentRegion(&fVisibleContentRegion);
fVisibleContentRegion.IntersectWith(&fVisibleRegion);
}
return fVisibleContentRegion;
}
void
WindowLayer::SetFocus(bool focus)
{
BRegion dirty(fBorderRegion);
dirty.IntersectWith(&fVisibleRegion);
fDesktop->MarkDirty(&dirty);
fFocus = focus;
}
void
WindowLayer::MoveBy(int32 x, int32 y)
{
if (x == 0 && y == 0)
return;
fFrame.OffsetBy(x, y);
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);
}
void
WindowLayer::ResizeBy(int32 x, int32 y, BRegion* dirtyRegion)
{
if (x == 0 && y == 0)
return;
fFrame.right += x;
fFrame.bottom += y;
dirtyRegion->Include(&fBorderRegion);
fBorderRegionValid = false;
fContentRegionValid = false;
fEffectiveDrawingRegionValid = false;
BRegion newBorderRegion;
GetBorderRegion(&newBorderRegion);
dirtyRegion->Include(&newBorderRegion);
fTopLayer->ResizeBy(x, y, dirtyRegion);
}
void
WindowLayer::ScrollViewBy(ViewLayer* view, int32 dx, int32 dy)
{
if (!view || view == fTopLayer || (dx == 0 && dy == 0))
return;
if (fDesktop && fDesktop->ReadLockClipping()) {
BRegion dirty;
view->ScrollBy(dx, dy, &dirty);
_MarkContentDirty(&dirty);
fDesktop->ReadUnlockClipping();
}
}
void
WindowLayer::AddChild(ViewLayer* layer)
{
fTopLayer->AddChild(layer);
fTokenViewMap.MakeEmpty();
fTopLayer->CollectTokensForChildren(&fTokenViewMap);
BMessage message(MSG_VIEWS_ADDED);
message.AddInt32("count", fTokenViewMap.CountItems());
fClient->PostMessage(&message);
}
ViewLayer*
WindowLayer::ViewAt(const BPoint& where)
{
if (!fContentRegionValid)
_UpdateContentRegion();
return fTopLayer->ViewAt(where, &fContentRegion);
}
void
WindowLayer::SetHidden(bool hidden)
{
if (fHidden != hidden) {
fHidden = hidden;
fTopLayer->SetHidden(hidden);
if (fHidden)
fClient->PostMessage(MSG_WINDOW_HIDDEN);
}
}
void
WindowLayer::ProcessDirtyRegion(BRegion* region)
{
if (fDirtyRegion.CountRects() == 0) {
PostMessage(MSG_REDRAW, this);
}
fDirtyRegion.Include(region);
}
void
WindowLayer::MarkDirty(BRegion* regionOnScreen)
{
if (fDesktop)
fDesktop->MarkDirty(regionOnScreen);
}
void
WindowLayer::MarkContentDirty(BRegion* regionOnScreen)
{
if (fDesktop && fDesktop->ReadLockClipping()) {
regionOnScreen->IntersectWith(&VisibleContentRegion());
ProcessDirtyRegion(regionOnScreen);
fDesktop->ReadUnlockClipping();
}
}
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();
}
}
void
WindowLayer::CopyContents(BRegion* region, int32 xOffset, int32 yOffset)
{
if (fDesktop->ReadLockClipping()) {
BRegion newDirty(*region);
region->IntersectWith(&VisibleContentRegion());
if (region->CountRects() > 0) {
region->OffsetBy(xOffset, yOffset);
region->IntersectWith(&fVisibleContentRegion);
if (region->CountRects() > 0) {
region->OffsetBy(-xOffset, -yOffset);
newDirty.Exclude(region);
if (fDrawingEngine->Lock()) {
fDrawingEngine->CopyRegion(region, xOffset, yOffset);
fDrawingEngine->Unlock();
}
_ShiftPartOfRegion(&fDirtyRegion, region, xOffset, yOffset);
if (fCurrentUpdateSession.IsUsed())
_ShiftPartOfRegion(&fCurrentUpdateSession.DirtyRegion(), region, xOffset, yOffset);
if (fPendingUpdateSession.IsUsed())
_ShiftPartOfRegion(&fPendingUpdateSession.DirtyRegion(), region, xOffset, yOffset);
}
}
newDirty.OffsetBy(xOffset, yOffset);
newDirty.IntersectWith(&fVisibleContentRegion);
if (newDirty.CountRects() > 0)
ProcessDirtyRegion(&newDirty);
fDesktop->ReadUnlockClipping();
}
}
void
WindowLayer::_ShiftPartOfRegion(BRegion* region, BRegion* regionToShift,
int32 xOffset, int32 yOffset)
{
BRegion common(*regionToShift);
common.IntersectWith(region);
if (common.CountRects() > 0) {
region->Exclude(&common);
common.OffsetBy(xOffset, yOffset);
region->Include(&common);
}
}
void
WindowLayer::_TriggerContentRedraw()
{
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
_MarkContentDirty(&dirtyContentRegion);
if (!fContentRegionValid)
_UpdateContentRegion();
#if DELAYED_BACKGROUND_CLEARING
fTopLayer->Draw(fDrawingEngine, &dirtyContentRegion,
&fContentRegion, false);
#else
fTopLayer->Draw(fDrawingEngine, &dirtyContentRegion,
&fContentRegion, true);
#endif
}
}
void
WindowLayer::_DrawClient(int32 token)
{
if (fDesktop->ReadLockClipping()) {
ViewLayer* layer = (ViewLayer*)fTokenViewMap.ItemAt(token);
if (!layer || !layer->IsVisible()) {
fDesktop->ReadUnlockClipping();
return;
}
if (!fEffectiveDrawingRegionValid) {
fEffectiveDrawingRegion = VisibleContentRegion();
if (fInUpdate) {
fEffectiveDrawingRegion.IntersectWith(&fCurrentUpdateSession.DirtyRegion());
} else {
printf("%s - _DrawClient(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->ClientDraw(fDrawingEngine, &effectiveClipping);
}
fDesktop->ReadUnlockClipping();
}
}
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) {
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->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();
}
}
void
WindowLayer::_DrawBorder()
{
BRegion dirtyBorderRegion;
GetBorderRegion(&dirtyBorderRegion);
dirtyBorderRegion.IntersectWith(&fVisibleRegion);
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();
}
}
}
void
WindowLayer::_MarkContentDirty(BRegion* contentDirtyRegion)
{
if (contentDirtyRegion->CountRects() <= 0)
return;
fPendingUpdateSession.SetUsed(true);
fPendingUpdateSession.Include(contentDirtyRegion);
if (fCurrentUpdateSession.IsUsed()) {
fCurrentUpdateSession.Exclude(contentDirtyRegion);
fEffectiveDrawingRegionValid = false;
}
if (!fUpdateRequested) {
fClient->PostMessage(MSG_UPDATE);
fUpdateRequested = true;
}
}
void
WindowLayer::_BeginUpdate()
{
if (fDesktop->ReadLockClipping()) {
if (fUpdateRequested && !fCurrentUpdateSession.IsUsed()) {
if (fPendingUpdateSession.IsUsed()) {
fCurrentUpdateSession = fPendingUpdateSession;
fPendingUpdateSession.SetUsed(false);
fInUpdate = true;
}
fEffectiveDrawingRegionValid = false;
}
fDesktop->ReadUnlockClipping();
}
}
void
WindowLayer::_EndUpdate()
{
if (fDesktop->ReadLockClipping()) {
if (fInUpdate) {
fCurrentUpdateSession.SetUsed(false);
fInUpdate = false;
fEffectiveDrawingRegionValid = false;
}
if (fPendingUpdateSession.IsUsed()) {
fClient->PostMessage(MSG_UPDATE);
fUpdateRequested = true;
} else {
fUpdateRequested = false;
}
fDesktop->ReadUnlockClipping();
}
}
void
WindowLayer::_UpdateContentRegion()
{
fContentRegion.Set(fFrame);
fContentRegion.Exclude(BRect(fFrame.right - 10, fFrame.bottom - 10,
fFrame.right, fFrame.bottom));
fContentRegionValid = true;
}
UpdateSession::UpdateSession()
: fDirtyRegion(),
fInUse(false)
{
}
UpdateSession::~UpdateSession()
{
}
void
UpdateSession::Include(BRegion* additionalDirty)
{
fDirtyRegion.Include(additionalDirty);
}
void
UpdateSession::Exclude(BRegion* dirtyInNextSession)
{
fDirtyRegion.Exclude(dirtyInNextSession);
}
void
UpdateSession::MoveBy(int32 x, int32 y)
{
fDirtyRegion.OffsetBy(x, y);
}
void
UpdateSession::SetUsed(bool used)
{
fInUse = used;
if (!fInUse) {
fDirtyRegion.MakeEmpty();
}
}
UpdateSession&
UpdateSession::operator=(const UpdateSession& other)
{
fDirtyRegion = other.fDirtyRegion;
fInUse = other.fInUse;
return *this;
}