/* * Copyright (c) 2001-2005, Haiku, Inc. * Distributed under the terms of the MIT license. * * Authors: * DarkWyrm * Adi Oanca * Stephan Aßmus */ #include "ViewLayer.h" #include "Desktop.h" #include "DrawingEngine.h" #include "ServerApp.h" #include "ServerWindow.h" #include "WindowLayer.h" #include #include // for resize modes #include #include using std::nothrow; ViewLayer::ViewLayer(BRect frame, const char* name, int32 token, uint32 resizeMode, uint32 flags) : fName(name), fToken(token), fFrame(frame), fScrollingOffset(0.0, 0.0), fViewColor(RGBColor(255, 255, 255)), fDrawState(new (nothrow) DrawState), fResizeMode(resizeMode), fFlags(flags), // ViewLayers start visible by default fHidden(false), fVisible(true), fEventMask(0), fEventOptions(0), fWindow(NULL), fParent(NULL), fFirstChild(NULL), fPreviousSibling(NULL), fNextSibling(NULL), fLastChild(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() { delete fDrawState; // 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; // insert view into local token space if (fWindow != NULL) fWindow->ServerWindow()->App()->ViewTokens().SetToken(fToken, B_HANDLER_TOKEN, this); // attach child views as well for (ViewLayer* child = FirstChild(); child; child = child->NextSibling()) child->AttachedToWindow(window); } // DetachedFromWindow void ViewLayer::DetachedFromWindow() { // remove view from local token space if (fWindow != NULL) fWindow->ServerWindow()->App()->ViewTokens().RemoveToken(fToken); fWindow = NULL; // detach child views as well for (ViewLayer* child = FirstChild(); child; child = child->NextSibling()) 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); } } } 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; } ViewLayer* ViewLayer::FirstChild() const { return fFirstChild; } ViewLayer* ViewLayer::PreviousSibling() const { return fPreviousSibling; } ViewLayer* ViewLayer::NextSibling() const { return fNextSibling; } ViewLayer* ViewLayer::LastChild() const { return fLastChild; } 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 = child->NextSibling()) { count++; if (deep) { count += child->CountChildren(deep); } } return count; } // CollectTokensForChildren void ViewLayer::CollectTokensForChildren(BList* tokenMap) const { for (ViewLayer* child = FirstChild(); child; child = child->NextSibling()) { 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 = child->NextSibling()) { ViewLayer* layer = child->ViewAt(where, windowContentClipping); if (layer) return layer; } return NULL; } void ViewLayer::SetFlags(uint32 flags) { fFlags = flags; fDrawState->SetSubPixelPrecise(fFlags & B_SUBPIXEL_PRECISE); } BPoint ViewLayer::ScrollingOffset() const { return fScrollingOffset; } void ViewLayer::SetDrawingOrigin(BPoint origin) { fDrawState->SetOrigin(origin); // rebuild clipping if (fDrawState->ClippingRegion()) RebuildClipping(false); } BPoint ViewLayer::DrawingOrigin() const { BPoint origin(fDrawState->Origin()); float scale = Scale(); origin.x *= scale; origin.y *= scale; return origin; } void ViewLayer::SetScale(float scale) { fDrawState->SetScale(scale); // rebuild clipping if (fDrawState->ClippingRegion()) RebuildClipping(false); } float ViewLayer::Scale() const { return CurrentState()->Scale(); } void ViewLayer::SetUserClipping(const BRegion& region) { fDrawState->SetClippingRegion(region); // rebuild clipping RebuildClipping(false); } 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); } //! converts a point from local to screen coordinate system void ViewLayer::ConvertToScreen(BPoint* pt) const { ConvertToParent(pt); if (fParent) fParent->ConvertToScreen(pt); } //! converts a rect from local to screen coordinate system void ViewLayer::ConvertToScreen(BRect* rect) const { ConvertToParent(rect); if (fParent) fParent->ConvertToScreen(rect); } //! converts a region from local to screen coordinate system void ViewLayer::ConvertToScreen(BRegion* reg) const { ConvertToParent(reg); if (fParent) fParent->ConvertToScreen(reg); } //! converts a point from screen to local coordinate system void ViewLayer::ConvertFromScreen(BPoint* pt) const { ConvertFromParent(pt); if (fParent) fParent->ConvertFromScreen(pt); } //! converts a rect from screen to local coordinate system void ViewLayer::ConvertFromScreen(BRect* rect) const { ConvertFromParent(rect); if (fParent) fParent->ConvertFromScreen(rect); } //! converts a region from screen to local coordinate system void ViewLayer::ConvertFromScreen(BRegion* reg) const { ConvertFromParent(reg); if (fParent) fParent->ConvertFromScreen(reg); } //! converts a point from local *drawing* to screen coordinate system void ViewLayer::ConvertToScreenForDrawing(BPoint* point) const { fDrawState->Transform(point); // NOTE: from here on, don't use the // "*ForDrawing()" versions of the parent! ConvertToScreen(point); } //! converts a rect from local *drawing* to screen coordinate system void ViewLayer::ConvertToScreenForDrawing(BRect* rect) const { fDrawState->Transform(rect); // NOTE: from here on, don't use the // "*ForDrawing()" versions of the parent! ConvertToScreen(rect); } //! converts a region from local *drawing* to screen coordinate system void ViewLayer::ConvertToScreenForDrawing(BRegion* region) const { fDrawState->Transform(region); // NOTE: from here on, don't use the // "*ForDrawing()" versions of the parent! ConvertToScreen(region); } //! converts a point from screen to local coordinate system void ViewLayer::ConvertFromScreenForDrawing(BPoint* point) const { ConvertFromScreen(point); fDrawState->InverseTransform(point); } void ViewLayer::SetName(const char* string) { fName.SetTo(string); } // #pragma mark - 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); ConvertToScreen(&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); ConvertToScreen(&oldVisibleBounds); BRect newVisibleBounds(Bounds()); // NOTE: using ConvertToVisibleInTopView() // instead of ConvertToScreen()! 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(©Region, 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 = child->NextSibling()) { if (child->IsVisible()) { BRect previousChildVisible(child->Frame() & oldBounds & Bounds()); if (dirty.Frame().Intersects(previousChildVisible)) { dirty.Exclude(previousChildVisible); } } } ConvertToScreen(&dirty); dirtyRegion->Include(&dirty); } } // layout the children for (ViewLayer* child = FirstChild(); child; child = child->NextSibling()) 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 ConvertToScreen(), 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(©Region, -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); } void ViewLayer::CopyBits(BRect src, BRect dst, BRegion& windowContentClipping) { if (!fVisible || !fWindow) return; // TODO: confirm that in R5 this call is affected by origin and scale // blitting version int32 xOffset = (int32)(dst.left - src.left); int32 yOffset = (int32)(dst.top - src.top); // figure out which part can be blittet BRect visibleSrc(src); ConvertToVisibleInTopView(&visibleSrc); BRect visibleSrcAtDest(src); visibleSrcAtDest.OffsetBy(xOffset, yOffset); ConvertToVisibleInTopView(&visibleSrcAtDest); // clip src to visible at dest visibleSrcAtDest.OffsetBy(-xOffset, -yOffset); visibleSrc = visibleSrc & visibleSrcAtDest; // do the blit, this will make sure // that other more complex dirty regions // are taken care of BRegion copyRegion(visibleSrc); copyRegion.IntersectWith(&ScreenClipping(&windowContentClipping)); fWindow->CopyContents(©Region, xOffset, yOffset); // find the dirty region as far as we are concerned BRect dirtyDst(dst); ConvertToVisibleInTopView(&dirtyDst); BRegion dirty(dirtyDst); // exclude the part that we could copy visibleSrc.OffsetBy(xOffset, yOffset); dirty.Exclude(visibleSrc); dirty.IntersectWith(&ScreenClipping(&windowContentClipping)); fWindow->MarkContentDirty(dirty); } // #pragma mark - void ViewLayer::PushState() { fDrawState = fDrawState->PushState(); fDrawState->SetSubPixelPrecise(fFlags & B_SUBPIXEL_PRECISE); } void ViewLayer::PopState() { if (fDrawState->PreviousState() == NULL) { fprintf(stderr, "WARNING: User called BView(%s)::PopState(), " "but there is NO state on stack!\n", Name()); return; } bool rebuildClipping = fDrawState->ClippingRegion() != NULL; fDrawState = fDrawState->PopState(); fDrawState->SetSubPixelPrecise(fFlags & B_SUBPIXEL_PRECISE); // rebuild clipping // (the clipping from the popped state is not effective anymore) if (rebuildClipping) RebuildClipping(false); } void ViewLayer::SetEventMask(uint32 eventMask, uint32 options) { fEventMask = eventMask; fEventOptions = options; } 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); if (!fViewColor.IsTransparentMagic()) { // fill visible region with view color drawingEngine->FillRegion(redraw, fViewColor); } // 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 = child->NextSibling()) { child->Draw(drawingEngine, effectiveClipping, windowContentClipping, deep); } } } // #pragma mark - void ViewLayer::SetHidden(bool hidden) { // TODO: test... if (fHidden != hidden) { fHidden = hidden; // recurse into children and update their visible flag if (fParent) { bool oldVisible = fVisible; UpdateVisibleDeep(fParent->IsVisible()); if (oldVisible != 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 = child->NextSibling()) child->UpdateVisibleDeep(fVisible); } // PrintToStream void ViewLayer::PrintToStream() const { printf("ViewLayer: %s\n", Name()); printf(" fToken: %ld\n", fToken); printf(" fFrame: BRect(%.1f, %.1f, %.1f, %.1f)\n", fFrame.left, fFrame.top, fFrame.right, fFrame.bottom); printf(" fScrollingOffset: BPoint(%.1f, %.1f)\n", fScrollingOffset.x, fScrollingOffset.y); printf(" fHidden: %d\n", fHidden); printf(" fVisible: %d\n", fVisible); printf(" fWindow: %p\n", fWindow); printf(" fParent: %p\n", fParent); printf(" fLocalClipping:\n"); fLocalClipping.PrintToStream(); printf(" fScreenClipping:\n"); fScreenClipping.PrintToStream(); printf(" valid: %d\n", fScreenClippingValid); printf(" state:\n"); printf(" user clipping: %p\n", fDrawState->ClippingRegion()); printf(" origin: BPoint(%.1f, %.1f)\n", fDrawState->Origin().x, fDrawState->Origin().y); printf(" scale: %.2f\n", fDrawState->Scale()); printf("\n"); } // 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 = child->NextSibling()) { if (child->IsVisible()) fLocalClipping.Exclude(child->Frame()); if (deep) child->RebuildClipping(deep); } // add the user clipping in case there is one if (const BRegion* userClipping = fDrawState->ClippingRegion()) { // NOTE: in case the user sets a user defined clipping region, // rebuilding the clipping is a bit more expensive because there // is no separate "drawing region"... on the other // hand, views for which this feature is actually used will // probably not have any children, so it is not that expensive // after all BRegion screenUserClipping(*userClipping); fDrawState->Transform(&screenUserClipping); fLocalClipping.IntersectWith(&screenUserClipping); } fScreenClippingValid = false; } // ScreenClipping BRegion& ViewLayer::ScreenClipping(BRegion* windowContentClipping, bool force) const { if (!fScreenClippingValid || force) { fScreenClipping = fLocalClipping; ConvertToScreen(&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; } //printf("###ViewLayer(%s)::ScreenClipping():\n", Name()); //fScreenClipping.PrintToStream(); 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 = child->NextSibling()) { 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 = child->NextSibling()) { child->_MoveScreenClipping(x, y, deep); } } }