#include #include #include #include #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; }