/* * Copyright (c) 2001-2008, Haiku, Inc. * Distributed under the terms of the MIT license. * * Authors: * DarkWyrm <bpmagic@columbus.rr.com> * Adi Oanca <adioanca@gmail.com> * Axel Dörfler, axeld@pinc-software.de * Stephan Aßmus <superstippi@gmx.de> * Marcus Overhagen <marcus@overhagen.de> */ #include "View.h" #include "BitmapManager.h" #include "Desktop.h" #include "DrawingEngine.h" #include "Overlay.h" #include "ServerApp.h" #include "ServerBitmap.h" #include "ServerCursor.h" #include "ServerPicture.h" #include "ServerWindow.h" #include "Window.h" #include "drawing_support.h" #include <List.h> #include <Message.h> #include <PortLink.h> #include <View.h> // for resize modes #include <WindowPrivate.h> #include <GradientLinear.h> #include <GradientRadial.h> #include <GradientRadialFocus.h> #include <GradientDiamond.h> #include <GradientConic.h> #include <stdio.h> #include <new> using std::nothrow; void resize_frame(IntRect& frame, uint32 resizingMode, int32 x, int32 y) { // follow with left side if ((resizingMode & 0x0F00U) == _VIEW_RIGHT_ << 8) frame.left += x; else if ((resizingMode & 0x0F00U) == _VIEW_CENTER_ << 8) frame.left += x / 2; // follow with right side if ((resizingMode & 0x000FU) == _VIEW_RIGHT_) frame.right += x; else if ((resizingMode & 0x000FU) == _VIEW_CENTER_) frame.right += x / 2; // follow with top side if ((resizingMode & 0xF000U) == _VIEW_BOTTOM_ << 12) frame.top += y; else if ((resizingMode & 0xF000U) == _VIEW_CENTER_ << 12) frame.top += y / 2; // follow with bottom side if ((resizingMode & 0x00F0U) == _VIEW_BOTTOM_ << 4) frame.bottom += y; else if ((resizingMode & 0x00F0U) == _VIEW_CENTER_ << 4) frame.bottom += y / 2; } // #pragma mark - View::View(IntRect frame, IntPoint scrollingOffset, const char* name, int32 token, uint32 resizeMode, uint32 flags) : fName(name), fToken(token), fFrame(frame), fScrollingOffset(scrollingOffset), fViewColor((rgb_color){ 255, 255, 255, 255 }), fDrawState(new (nothrow) DrawState), fViewBitmap(NULL), fResizeMode(resizeMode), fFlags(flags), // Views start visible by default fHidden(false), fVisible(true), fBackgroundDirty(true), fIsDesktopBackground(false), fEventMask(0), fEventOptions(0), fWindow(NULL), fParent(NULL), fFirstChild(NULL), fPreviousSibling(NULL), fNextSibling(NULL), fLastChild(NULL), fCursor(NULL), fPicture(NULL), fLocalClipping((BRect)Bounds()), fScreenClipping(), fScreenClippingValid(false), fUserClipping(NULL), fScreenAndUserClipping(NULL) { if (fDrawState) fDrawState->SetSubPixelPrecise(fFlags & B_SUBPIXEL_PRECISE); } View::~View() { if (fViewBitmap != NULL) gBitmapManager->DeleteBitmap(fViewBitmap); delete fScreenAndUserClipping; delete fUserClipping; delete fDrawState; // if (fWindow && this == fWindow->TopView()) // fWindow->SetTopView(NULL); if (fCursor) fCursor->Release(); // iterate over children and delete each one View* view = fFirstChild; while (view) { View* toast = view; view = view->fNextSibling; delete toast; } } IntRect View::Bounds() const { IntRect bounds(fScrollingOffset.x, fScrollingOffset.y, fScrollingOffset.x + fFrame.Width(), fScrollingOffset.y + fFrame.Height()); return bounds; } void View::ConvertToVisibleInTopView(IntRect* 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); } void View::AttachedToWindow(::Window* window) { fWindow = window; // an ugly hack to detect the desktop background if (window->Feel() == kDesktopWindowFeel && Parent() == TopView()) fIsDesktopBackground = true; // 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 (View* child = FirstChild(); child; child = child->NextSibling()) child->AttachedToWindow(window); } void View::DetachedFromWindow() { // remove view from local token space if (fWindow != NULL) fWindow->ServerWindow()->App()->ViewTokens().RemoveToken(fToken); fWindow = NULL; // detach child views as well for (View* child = FirstChild(); child; child = child->NextSibling()) child->DetachedFromWindow(); } // #pragma mark - void View::AddChild(View* view) { if (view->fParent) { printf("View::AddChild() - View already has a parent\n"); return; } view->fParent = this; if (!fLastChild) { // no children yet fFirstChild = view; } else { // append view to formerly last child fLastChild->fNextSibling = view; view->fPreviousSibling = fLastChild; } fLastChild = view; view->UpdateVisibleDeep(fVisible); if (view->IsVisible()) RebuildClipping(false); if (fWindow) { view->AttachedToWindow(fWindow); if (view->IsVisible()) { // trigger redraw IntRect clippedFrame = view->Frame(); ConvertToVisibleInTopView(&clippedFrame); BRegion* dirty = fWindow->GetRegion(); if (dirty) { dirty->Set((clipping_rect)clippedFrame); fWindow->MarkContentDirtyAsync(*dirty); fWindow->RecycleRegion(dirty); } } } } bool View::RemoveChild(View* view) { if (view == NULL || view->fParent != this) { printf("View::RemoveChild(%p - %s) - View is not child of " "this (%p) view!\n", view, view ? view->Name() : NULL, this); return false; } view->fParent = NULL; if (fLastChild == view) fLastChild = view->fPreviousSibling; // view->fNextSibling would be NULL if (fFirstChild == view ) fFirstChild = view->fNextSibling; // view->fPreviousSibling would be NULL // connect child before and after view if (view->fPreviousSibling) view->fPreviousSibling->fNextSibling = view->fNextSibling; if (view->fNextSibling) view->fNextSibling->fPreviousSibling = view->fPreviousSibling; // view has no siblings anymore view->fPreviousSibling = NULL; view->fNextSibling = NULL; if (view->IsVisible()) { Overlay* overlay = view->_Overlay(); if (overlay != NULL) overlay->Hide(); RebuildClipping(false); } if (fWindow) { view->DetachedFromWindow(); if (fVisible && view->IsVisible()) { // trigger redraw IntRect clippedFrame = view->Frame(); ConvertToVisibleInTopView(&clippedFrame); BRegion* dirty = fWindow->GetRegion(); if (dirty) { dirty->Set((clipping_rect)clippedFrame); fWindow->MarkContentDirtyAsync(*dirty); fWindow->RecycleRegion(dirty); } } } return true; } View* View::TopView() { // returns the top level view of the hirarchy, // it doesn't have to be the top level of a window if (fParent) return fParent->TopView(); return this; } uint32 View::CountChildren(bool deep) const { uint32 count = 0; for (View* child = FirstChild(); child; child = child->NextSibling()) { count++; if (deep) { count += child->CountChildren(deep); } } return count; } void View::CollectTokensForChildren(BList* tokenMap) const { for (View* child = FirstChild(); child; child = child->NextSibling()) { tokenMap->AddItem((void*)child); child->CollectTokensForChildren(tokenMap); } } #if 0 bool View::MarkAt(DrawingEngine* engine, const BPoint& where, int32 level) { BRect rect(fFrame.left, fFrame.top, fFrame.right, fFrame.bottom); if (Parent() != NULL) { Parent()->ConvertToScreen(&rect); if (!rect.Contains(where)) return false; engine->StrokeRect(rect, (rgb_color){level * 30, level * 30, level * 30}); } bool found = false; for (View* child = FirstChild(); child; child = child->NextSibling()) { found |= child->MarkAt(engine, where, level + 1); } if (!found) { rgb_color color = {0}; switch (level % 2) { case 0: color.green = rand() % 256; break; case 1: color.blue = rand() % 256; break; } rect.InsetBy(1, 1); //engine->FillRegion(fLocalClipping, (rgb_color){255, 255, 0, 10}); engine->StrokeRect(rect, color); rect.InsetBy(1, 1); engine->StrokeRect(rect, color); } return true; } #endif void View::FindViews(uint32 flags, BObjectList<View>& list, int32& left) { if ((Flags() & flags) == flags) { list.AddItem(this); left--; return; } for (View* child = FirstChild(); child; child = child->NextSibling()) { child->FindViews(flags, list, left); if (left == 0) break; } } View* View::ViewAt(const BPoint& where) { if (!fVisible) return NULL; IntRect frame = Frame(); if (Parent() != NULL) Parent()->ConvertToScreen(&frame); if (!frame.Contains(where)) return NULL; for (View* child = FirstChild(); child; child = child->NextSibling()) { View* view = child->ViewAt(where); if (view != NULL) return view; } return this; } // #pragma mark - void View::SetName(const char* string) { fName.SetTo(string); } void View::SetFlags(uint32 flags) { fFlags = flags; fDrawState->SetSubPixelPrecise(fFlags & B_SUBPIXEL_PRECISE); } void View::SetDrawingOrigin(BPoint origin) { fDrawState->SetOrigin(origin); // rebuild clipping if (fDrawState->HasClipping()) RebuildClipping(false); } BPoint View::DrawingOrigin() const { BPoint origin(fDrawState->Origin()); float scale = Scale(); origin.x *= scale; origin.y *= scale; return origin; } void View::SetScale(float scale) { fDrawState->SetScale(scale); // rebuild clipping if (fDrawState->HasClipping()) RebuildClipping(false); } float View::Scale() const { return CurrentState()->Scale(); } void View::SetUserClipping(const BRegion* region) { fDrawState->SetClippingRegion(region); // rebuild clipping (for just this view) RebuildClipping(false); } void View::SetViewBitmap(ServerBitmap* bitmap, IntRect sourceRect, IntRect destRect, int32 resizingMode, int32 options) { if (fViewBitmap != NULL) { Overlay* overlay = _Overlay(); if (bitmap != NULL) { // take over overlay token from current overlay (if it has any) Overlay* newOverlay = bitmap->Overlay(); if (overlay != NULL && newOverlay != NULL) newOverlay->TakeOverToken(overlay); } else if (overlay != NULL) overlay->Hide(); gBitmapManager->DeleteBitmap(fViewBitmap); } // the caller is allowed to delete the bitmap after setting the background if (bitmap != NULL) bitmap->Acquire(); fViewBitmap = bitmap; fBitmapSource = sourceRect; fBitmapDestination = destRect; fBitmapResizingMode = resizingMode; fBitmapOptions = options; _UpdateOverlayView(); } ::Overlay* View::_Overlay() const { if (fViewBitmap == NULL) return NULL; return fViewBitmap->Overlay(); } void View::_UpdateOverlayView() const { Overlay* overlay = _Overlay(); if (overlay == NULL) return; IntRect destination = fBitmapDestination; ConvertToScreen(&destination); overlay->Configure(fBitmapSource, destination); } /*! This method is called whenever the window is resized or moved - would be nice to have a better solution for this, though. */ void View::UpdateOverlay() { if (!IsVisible()) return; if (_Overlay() != NULL) { _UpdateOverlayView(); } else { // recursively ask children of this view for (View* child = FirstChild(); child; child = child->NextSibling()) { child->UpdateOverlay(); } } } // #pragma mark - void View::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; } void View::ConvertToParent(IntPoint* point) const { // remove scrolling offset and convert to parent coordinate space point->x += fFrame.left - fScrollingOffset.x; point->y += fFrame.top - fScrollingOffset.y; } void View::ConvertToParent(BRect* rect) const { // remove scrolling offset and convert to parent coordinate space rect->OffsetBy(fFrame.left - fScrollingOffset.x, fFrame.top - fScrollingOffset.y); } void View::ConvertToParent(IntRect* rect) const { // remove scrolling offset and convert to parent coordinate space rect->OffsetBy(fFrame.left - fScrollingOffset.x, fFrame.top - fScrollingOffset.y); } void View::ConvertToParent(BRegion* region) const { // remove scrolling offset and convert to parent coordinate space region->OffsetBy(fFrame.left - fScrollingOffset.x, fFrame.top - fScrollingOffset.y); } void View::ConvertFromParent(BPoint* point) const { // convert from parent coordinate space amd add scrolling offset point->x += fScrollingOffset.x - fFrame.left; point->y += fScrollingOffset.y - fFrame.top; } void View::ConvertFromParent(IntPoint* point) const { // convert from parent coordinate space amd add scrolling offset point->x += fScrollingOffset.x - fFrame.left; point->y += fScrollingOffset.y - fFrame.top; } void View::ConvertFromParent(BRect* rect) const { // convert from parent coordinate space amd add scrolling offset rect->OffsetBy(fScrollingOffset.x - fFrame.left, fScrollingOffset.y - fFrame.top); } void View::ConvertFromParent(IntRect* rect) const { // convert from parent coordinate space amd add scrolling offset rect->OffsetBy(fScrollingOffset.x - fFrame.left, fScrollingOffset.y - fFrame.top); } void View::ConvertFromParent(BRegion* region) const { // convert from parent coordinate space amd add scrolling offset region->OffsetBy(fScrollingOffset.x - fFrame.left, fScrollingOffset.y - fFrame.top); } //! converts a point from local to screen coordinate system void View::ConvertToScreen(BPoint* pt) const { ConvertToParent(pt); if (fParent) fParent->ConvertToScreen(pt); } //! converts a point from local to screen coordinate system void View::ConvertToScreen(IntPoint* pt) const { ConvertToParent(pt); if (fParent) fParent->ConvertToScreen(pt); } //! converts a rect from local to screen coordinate system void View::ConvertToScreen(BRect* rect) const { BPoint offset(0.0, 0.0); ConvertToScreen(&offset); rect->OffsetBy(offset); } //! converts a rect from local to screen coordinate system void View::ConvertToScreen(IntRect* rect) const { BPoint offset(0.0, 0.0); ConvertToScreen(&offset); rect->OffsetBy(offset); } //! converts a region from local to screen coordinate system void View::ConvertToScreen(BRegion* region) const { BPoint offset(0.0, 0.0); ConvertToScreen(&offset); region->OffsetBy((int)offset.x, (int)offset.y); } //! converts a point from screen to local coordinate system void View::ConvertFromScreen(BPoint* pt) const { ConvertFromParent(pt); if (fParent) fParent->ConvertFromScreen(pt); } //! converts a point from screen to local coordinate system void View::ConvertFromScreen(IntPoint* pt) const { ConvertFromParent(pt); if (fParent) fParent->ConvertFromScreen(pt); } //! converts a rect from screen to local coordinate system void View::ConvertFromScreen(BRect* rect) const { BPoint offset(0.0, 0.0); ConvertFromScreen(&offset); rect->OffsetBy(offset.x, offset.y); } //! converts a rect from screen to local coordinate system void View::ConvertFromScreen(IntRect* rect) const { BPoint offset(0.0, 0.0); ConvertFromScreen(&offset); rect->OffsetBy((int)offset.x, (int)offset.y); } //! converts a region from screen to local coordinate system void View::ConvertFromScreen(BRegion* region) const { BPoint offset(0.0, 0.0); ConvertFromScreen(&offset); region->OffsetBy((int)offset.x, (int)offset.y); } //! converts a point from local *drawing* to screen coordinate system void View::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 View::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 View::ConvertToScreenForDrawing(BRegion* region) const { fDrawState->Transform(region); // NOTE: from here on, don't use the // "*ForDrawing()" versions of the parent! ConvertToScreen(region); } //! converts a gradient from local *drawing* to screen coordinate system void View::ConvertToScreenForDrawing(BGradient* gradient) const { switch(gradient->GetType()) { case BGradient::TYPE_LINEAR: { BGradientLinear* linear = (BGradientLinear*) gradient; BPoint start = linear->Start(); BPoint end = linear->End(); fDrawState->Transform(&start); ConvertToScreen(&start); fDrawState->Transform(&end); ConvertToScreen(&end); linear->SetStart(start); linear->SetEnd(end); linear->SortColorStopsByOffset(); break; } case BGradient::TYPE_RADIAL: { BGradientRadial* radial = (BGradientRadial*) gradient; BPoint center = radial->Center(); fDrawState->Transform(¢er); ConvertToScreen(¢er); radial->SetCenter(center); radial->SortColorStopsByOffset(); break; } case BGradient::TYPE_RADIAL_FOCUS: { BGradientRadialFocus* radialFocus = (BGradientRadialFocus*) gradient; BPoint center = radialFocus->Center(); BPoint focal = radialFocus->Focal(); fDrawState->Transform(¢er); ConvertToScreen(¢er); fDrawState->Transform(&focal); ConvertToScreen(&focal); radialFocus->SetCenter(center); radialFocus->SetFocal(focal); radialFocus->SortColorStopsByOffset(); break; } case BGradient::TYPE_DIAMOND: { BGradientDiamond* diamond = (BGradientDiamond*) gradient; BPoint center = diamond->Center(); fDrawState->Transform(¢er); ConvertToScreen(¢er); diamond->SetCenter(center); diamond->SortColorStopsByOffset(); break; } case BGradient::TYPE_CONIC: { BGradientConic* conic = (BGradientConic*) gradient; BPoint center = conic->Center(); fDrawState->Transform(¢er); ConvertToScreen(¢er); conic->SetCenter(center); conic->SortColorStopsByOffset(); break; } case BGradient::TYPE_NONE: { break; } } } //! converts points from local *drawing* to screen coordinate system void View::ConvertToScreenForDrawing(BPoint* dst, const BPoint* src, int32 num) const { // TODO: optimize this, it should be smarter while (num--) { *dst = *src; fDrawState->Transform(dst); // NOTE: from here on, don't use the // "*ForDrawing()" versions of the parent! ConvertToScreen(dst); src++; dst++; } } //! converts rects from local *drawing* to screen coordinate system void View::ConvertToScreenForDrawing(BRect* dst, const BRect* src, int32 num) const { // TODO: optimize this, it should be smarter while (num--) { *dst = *src; fDrawState->Transform(dst); // NOTE: from here on, don't use the // "*ForDrawing()" versions of the parent! ConvertToScreen(dst); src++; dst++; } } //! converts regions from local *drawing* to screen coordinate system void View::ConvertToScreenForDrawing(BRegion* dst, const BRegion* src, int32 num) const { // TODO: optimize this, it should be smarter while (num--) { *dst = *src; fDrawState->Transform(dst); // NOTE: from here on, don't use the // "*ForDrawing()" versions of the parent! ConvertToScreen(dst); src++; dst++; } } //! converts a point from screen to local coordinate system void View::ConvertFromScreenForDrawing(BPoint* point) const { ConvertFromScreen(point); fDrawState->InverseTransform(point); } // #pragma mark - void View::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 1 // based on redraw on new location // the place were we are now visible IntRect newVisibleBounds(Bounds()); // we can use the frame of the old // local clipping to see which parts need invalidation IntRect oldVisibleBounds(newVisibleBounds); oldVisibleBounds.OffsetBy(-x, -y); ConvertToScreen(&oldVisibleBounds); ConvertToVisibleInTopView(&newVisibleBounds); dirtyRegion->Include((clipping_rect)oldVisibleBounds); // newVisibleBounds already is in screen coords dirtyRegion->Include((clipping_rect)newVisibleBounds); #else // blitting version, invalidates // old contents IntRect oldVisibleBounds(Bounds()); IntRect newVisibleBounds(oldVisibleBounds); oldVisibleBounds.OffsetBy(-x, -y); ConvertToScreen(&oldVisibleBounds); // 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* region = fWindow->GetRegion(); if (region) { region->Set(oldVisibleBounds & newVisibleBounds); fWindow->CopyContents(region, x, y); region->Set(oldVisibleBounds); newVisibleBounds.OffsetBy(x, y); region->Exclude((clipping_rect)newVisibleBounds); dirtyRegion->Include(dirty); fWindow->RecycleRegion(region); } #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(); } } void View::ResizeBy(int32 x, int32 y, BRegion* dirtyRegion) { if (x == 0 && y == 0) return; fFrame.right += x; fFrame.bottom += y; if (fVisible && dirtyRegion) { IntRect oldBounds(Bounds()); oldBounds.right -= x; oldBounds.bottom -= y; BRegion* dirty = fWindow->GetRegion(); if (!dirty) return; dirty->Set((clipping_rect)Bounds()); dirty->Include((clipping_rect)oldBounds); if (!(fFlags & B_FULL_UPDATE_ON_RESIZE)) { // the dirty region is just the difference of // old and new bounds dirty->Exclude((clipping_rect)(oldBounds & Bounds())); } InvalidateScreenClipping(); if (dirty->CountRects() > 0) { if ((fFlags & B_DRAW_ON_CHILDREN) == 0) { // exclude children, they are expected to // include their own dirty regions in ParentResized() for (View* child = FirstChild(); child; child = child->NextSibling()) { if (!child->IsVisible()) continue; IntRect previousChildVisible( child->Frame() & oldBounds & Bounds()); if (dirty->Frame().Intersects(previousChildVisible)) { dirty->Exclude((clipping_rect)previousChildVisible); } } } ConvertToScreen(dirty); dirtyRegion->Include(dirty); } fWindow->RecycleRegion(dirty); } // layout the children for (View* child = FirstChild(); child; child = child->NextSibling()) child->ParentResized(x, y, dirtyRegion); // view bitmap resize_frame(fBitmapDestination, fBitmapResizingMode, x, y); // 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); } void View::ParentResized(int32 x, int32 y, BRegion* dirtyRegion) { IntRect newFrame = fFrame; resize_frame(newFrame, fResizeMode & 0x0000ffff, x, y); 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); } else { // TODO: this covers the fact that our screen clipping might change // when the parent changes its size, even though our frame stays // the same - there might be a way to test for this, but axeld doesn't // know, stippi should look into this when he's back :) InvalidateScreenClipping(); } } void View::ScrollBy(int32 x, int32 y, BRegion* dirtyRegion) { if (!fVisible || !fWindow) { fScrollingOffset.x += x; fScrollingOffset.y += y; return; } // blitting version, invalidates // old contents // remember old bounds for tracking dirty region IntRect oldBounds(Bounds()); // 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); // find the area of the view that can be scrolled, // contents are shifted in the opposite direction from scrolling IntRect stillVisibleBounds(oldBounds); stillVisibleBounds.OffsetBy(x, y); stillVisibleBounds = stillVisibleBounds & oldBounds; 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 = fWindow->GetRegion(); if (!copyRegion) return; copyRegion->Set((clipping_rect)stillVisibleBounds); fWindow->CopyContents(copyRegion, -x, -y); // find the dirty region as far as we are // concerned BRegion* dirty = copyRegion; // reuse copyRegion and call it dirty dirty->Set((clipping_rect)oldBounds); stillVisibleBounds.OffsetBy(-x, -y); dirty->Exclude((clipping_rect)stillVisibleBounds); dirtyRegion->Include(dirty); fWindow->RecycleRegion(dirty); // the screen clipping of this view and it's // childs is no longer valid InvalidateScreenClipping(); RebuildClipping(false); } void View::CopyBits(IntRect src, IntRect dst, BRegion& windowContentClipping) { if (!fVisible || !fWindow) return; // TODO: confirm that in R5 this call is affected by origin and scale // blitting version int32 xOffset = dst.left - src.left; int32 yOffset = dst.top - src.top; // figure out which part can be blittet IntRect visibleSrc(src); ConvertToVisibleInTopView(&visibleSrc); IntRect 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 = fWindow->GetRegion(); if (!copyRegion) return; // move src rect to destination here for efficiency reasons visibleSrc.OffsetBy(xOffset, yOffset); // we need to interstect the copyRegion two times, onces // at the source and once at the destination (here done // the other way arround but it doesn't matter) // the reason for this is that we are not supposed to visually // copy children in the source rect and neither to copy onto // children in the destination rect... copyRegion->Set((clipping_rect)visibleSrc); BRegion *screenAndUserClipping = &ScreenAndUserClipping(&windowContentClipping); copyRegion->IntersectWith(screenAndUserClipping); copyRegion->OffsetBy(-xOffset, -yOffset); copyRegion->IntersectWith(screenAndUserClipping); // do the actual blit fWindow->CopyContents(copyRegion, xOffset, yOffset); // find the dirty region as far as we are concerned IntRect dirtyDst(dst); ConvertToVisibleInTopView(&dirtyDst); BRegion* dirty = fWindow->GetRegion(); if (!dirty) { fWindow->RecycleRegion(copyRegion); return; } // offset copyRegion to destination again copyRegion->OffsetBy(xOffset, yOffset); // start with destination given by user dirty->Set((clipping_rect)dirtyDst); // exclude the part that we could copy dirty->Exclude(copyRegion); dirty->IntersectWith(screenAndUserClipping); fWindow->MarkContentDirty(*dirty); fWindow->RecycleRegion(dirty); fWindow->RecycleRegion(copyRegion); } // #pragma mark - void View::PushState() { DrawState* newState = fDrawState->PushState(); if (newState) { fDrawState = newState; fDrawState->SetSubPixelPrecise(fFlags & B_SUBPIXEL_PRECISE); } } void View::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->HasAdditionalClipping(); 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 View::SetEventMask(uint32 eventMask, uint32 options) { fEventMask = eventMask; fEventOptions = options; } void View::SetCursor(ServerCursor *cursor) { if (cursor == fCursor) return; if (fCursor) fCursor->Release(); fCursor = cursor; if (fCursor) fCursor->Acquire(); } void View::SetPicture(ServerPicture *picture) { fPicture = picture; } void View::Draw(DrawingEngine* drawingEngine, BRegion* effectiveClipping, BRegion* windowContentClipping, bool deep) { if (!fVisible) { // child views cannot be visible either return; } if (fViewBitmap != NULL || fViewColor != B_TRANSPARENT_COLOR) { // we can only draw within our own area BRegion* redraw; if ((fFlags & B_DRAW_ON_CHILDREN) != 0) { // The client may actually want to prevent the background to // be painted outside the user clipping. redraw = fWindow->GetRegion( ScreenAndUserClipping(windowContentClipping)); } else { // Ignore user clipping as in BeOS for painting the background. redraw = fWindow->GetRegion( _ScreenClipping(windowContentClipping)); } if (!redraw) return; // add the current clipping redraw->IntersectWith(effectiveClipping); Overlay* overlayCookie = _Overlay(); if (fViewBitmap != NULL && overlayCookie == NULL) { // draw view bitmap // TODO: support other options! BRect rect = fBitmapDestination; ConvertToScreenForDrawing(&rect); align_rect_to_pixels(&rect); if (fBitmapOptions & B_TILE_BITMAP_Y) { // move rect up as much as needed while (rect.top > redraw->Frame().top) rect.OffsetBy(0.0, -(rect.Height() + 1)); } if (fBitmapOptions & B_TILE_BITMAP_X) { // move rect left as much as needed while (rect.left > redraw->Frame().left) rect.OffsetBy(-(rect.Width() + 1), 0.0); } // XXX: locking removed because the Window keeps the engine locked // because it keeps track of syncing right now // lock the drawing engine for as long as we need the clipping // to be valid if (rect.IsValid()/* && drawingEngine->Lock()*/) { drawingEngine->ConstrainClippingRegion(redraw); drawing_mode oldMode; drawingEngine->SetDrawingMode(B_OP_COPY, oldMode); if (fBitmapOptions & B_TILE_BITMAP) { // tile across entire view float start = rect.left; while (rect.top < redraw->Frame().bottom) { while (rect.left < redraw->Frame().right) { drawingEngine->DrawBitmap(fViewBitmap, fBitmapSource, rect, fBitmapOptions); rect.OffsetBy(rect.Width() + 1, 0.0); } rect.OffsetBy(start - rect.left, rect.Height() + 1); } // nothing left to be drawn redraw->MakeEmpty(); } else if (fBitmapOptions & B_TILE_BITMAP_X) { // tile in x direction while (rect.left < redraw->Frame().right) { drawingEngine->DrawBitmap(fViewBitmap, fBitmapSource, rect, fBitmapOptions); rect.OffsetBy(rect.Width() + 1, 0.0); } // remove horizontal stripe from clipping rect.left = redraw->Frame().left; rect.right = redraw->Frame().right; redraw->Exclude(rect); } else if (fBitmapOptions & B_TILE_BITMAP_Y) { // tile in y direction while (rect.top < redraw->Frame().bottom) { drawingEngine->DrawBitmap(fViewBitmap, fBitmapSource, rect, fBitmapOptions); rect.OffsetBy(0.0, rect.Height() + 1); } // remove vertical stripe from clipping rect.top = redraw->Frame().top; rect.bottom = redraw->Frame().bottom; redraw->Exclude(rect); } else { // no tiling at all drawingEngine->DrawBitmap(fViewBitmap, fBitmapSource, rect, fBitmapOptions); redraw->Exclude(rect); } drawingEngine->SetDrawingMode(oldMode); // NOTE: It is ok not to reset the clipping, that // would only waste time // drawingEngine->Unlock(); } } if (fViewColor != B_TRANSPARENT_COLOR) { // fill visible region with view color, // this version of FillRegion ignores any // clipping, that's why "redraw" needs to // be correct // see #634 // if (redraw->Frame().left < 0 || redraw->Frame().top < 0) { // char message[1024]; // BRect c = effectiveClipping->Frame(); // BRect w = windowContentClipping->Frame(); // BRect r = redraw->Frame(); // sprintf(message, "invalid background: current clipping: (%d, %d)->(%d, %d), " // "window content: (%d, %d)->(%d, %d), redraw: (%d, %d)->(%d, %d)", // (int)c.left, (int)c.top, (int)c.right, (int)c.bottom, // (int)w.left, (int)w.top, (int)w.right, (int)w.bottom, // (int)r.left, (int)r.top, (int)r.right, (int)r.bottom); // debugger(message); // } drawingEngine->FillRegion(*redraw, overlayCookie != NULL ? overlayCookie->Color() : fViewColor); } fWindow->RecycleRegion(redraw); } fBackgroundDirty = false; // let children draw if (deep) { for (View* child = FirstChild(); child; child = child->NextSibling()) { child->Draw(drawingEngine, effectiveClipping, windowContentClipping, deep); } } } // #pragma mark - void View::MouseDown(BMessage* message, BPoint where) { // empty hook method } void View::MouseUp(BMessage* message, BPoint where) { // empty hook method } void View::MouseMoved(BMessage* message, BPoint where) { // empty hook method } // #pragma mark - void View::SetHidden(bool hidden) { if (fHidden != hidden) { fHidden = hidden; // recurse into children and update their visible flag bool oldVisible = fVisible; UpdateVisibleDeep(fParent ? fParent->IsVisible() : !fHidden); if (oldVisible != fVisible) { // Include or exclude us from the parent area, and update the // children's clipping as well when the view will be visible if (fParent) fParent->RebuildClipping(fVisible); else RebuildClipping(fVisible); if (fWindow) { // trigger a redraw IntRect clippedBounds = Bounds(); ConvertToVisibleInTopView(&clippedBounds); BRegion* dirty = fWindow->GetRegion(); if (!dirty) return; dirty->Set((clipping_rect)clippedBounds); fWindow->MarkContentDirty(*dirty); fWindow->RecycleRegion(dirty); } } } } bool View::IsHidden() const { return fHidden; } void View::UpdateVisibleDeep(bool parentVisible) { bool wasVisible = fVisible; fVisible = parentVisible && !fHidden; for (View* child = FirstChild(); child; child = child->NextSibling()) child->UpdateVisibleDeep(fVisible); // overlay handling Overlay* overlay = _Overlay(); if (overlay == NULL) return; if (fVisible && !wasVisible) _UpdateOverlayView(); else if (!fVisible && wasVisible) overlay->Hide(); } // #pragma mark - void View::MarkBackgroundDirty() { if (fBackgroundDirty) return; fBackgroundDirty = true; for (View* child = FirstChild(); child; child = child->NextSibling()) child->MarkBackgroundDirty(); } void View::AddTokensForViewsInRegion(BPrivate::PortLink& link, BRegion& region, BRegion* windowContentClipping) { if (!fVisible) return; { // NOTE: use scope in order to reduce stack space requirements // This check will prevent descending the view hierarchy // any further than necessary IntRect screenBounds(Bounds()); ConvertToScreen(&screenBounds); if (!region.Intersects((clipping_rect)screenBounds)) return; // Unfortunately, we intersecting another region, but otherwise // we couldn't provide the exact update rect to the client BRegion localDirty = _ScreenClipping(windowContentClipping); localDirty.IntersectWith(®ion); if (localDirty.CountRects() > 0) { link.Attach<int32>(fToken); link.Attach<BRect>(localDirty.Frame()); } } for (View* child = FirstChild(); child; child = child->NextSibling()) child->AddTokensForViewsInRegion(link, region, windowContentClipping); } void View::PrintToStream() const { printf("View: %s\n", Name()); printf(" fToken: %ld\n", fToken); printf(" fFrame: IntRect(%ld, %ld, %ld, %ld)\n", fFrame.left, fFrame.top, fFrame.right, fFrame.bottom); printf(" fScrollingOffset: IntPoint(%ld, %ld)\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(" fUserClipping:\n"); if (fUserClipping != NULL) fUserClipping->PrintToStream(); else printf(" none\n"); printf(" fScreenAndUserClipping:\n"); if (fScreenAndUserClipping != NULL) fScreenAndUserClipping->PrintToStream(); else printf(" invalid\n"); printf(" state:\n"); printf(" user clipping: %d\n", fDrawState->HasClipping()); BPoint origin = fDrawState->CombinedOrigin(); printf(" origin: BPoint(%.1f, %.1f)\n", origin.x, origin.y); printf(" scale: %.2f\n", fDrawState->CombinedScale()); printf("\n"); } void View::RebuildClipping(bool deep) { // the clipping spans over the bounds area fLocalClipping.Set((clipping_rect)Bounds()); if (View* child = FirstChild()) { // if this view does not draw over children, // exclude all children from the clipping if ((fFlags & B_DRAW_ON_CHILDREN) == 0) { BRegion* childrenRegion = fWindow->GetRegion(); if (!childrenRegion) return; for (; child; child = child->NextSibling()) { if (child->IsVisible()) childrenRegion->Include((clipping_rect)child->Frame()); } fLocalClipping.Exclude(childrenRegion); fWindow->RecycleRegion(childrenRegion); } // if the operation is "deep", make children rebuild their // clipping too if (deep) { for (child = FirstChild(); child; child = child->NextSibling()) child->RebuildClipping(true); } } // add the user clipping in case there is one if (fDrawState->HasClipping()) { // 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 if (fUserClipping == NULL) { fUserClipping = new (nothrow) BRegion; if (fUserClipping == NULL) return; } fDrawState->GetCombinedClippingRegion(fUserClipping); } else { delete fUserClipping; fUserClipping = NULL; } delete fScreenAndUserClipping; fScreenAndUserClipping = NULL; fScreenClippingValid = false; } BRegion& View::ScreenAndUserClipping(BRegion* windowContentClipping, bool force) const { // no user clipping - return screen clipping directly if (fUserClipping == NULL) return _ScreenClipping(windowContentClipping, force); // combined screen and user clipping already valid if (fScreenAndUserClipping != NULL) return *fScreenAndUserClipping; // build a new combined user and screen clipping fScreenAndUserClipping = new (nothrow) BRegion(*fUserClipping); if (fScreenAndUserClipping == NULL) return fScreenClipping; ConvertToScreen(fScreenAndUserClipping); fScreenAndUserClipping->IntersectWith( &_ScreenClipping(windowContentClipping, force)); return *fScreenAndUserClipping; } void View::InvalidateScreenClipping() { // TODO: appearantly, we are calling ScreenClipping() on // views who's parents don't have a valid screen clipping yet, // this messes up the logic that for any given view with // fScreenClippingValid == false, all children have // fScreenClippingValid == false too. If this could be made the // case, we could save some performance here with the commented // out check, since InvalidateScreenClipping() might be called // frequently. // TODO: investigate, if InvalidateScreenClipping() could be // called in "deep" and "non-deep" mode, ie. see if there are // any cases where the children would still have valid screen // clipping, even though the parent's screen clipping becomes // invalid. // if (!fScreenClippingValid) // return; delete fScreenAndUserClipping; fScreenAndUserClipping = NULL; fScreenClippingValid = false; // invalidate the childrens screen clipping as well for (View* child = FirstChild(); child; child = child->NextSibling()) { child->InvalidateScreenClipping(); } } BRegion& View::_ScreenClipping(BRegion* windowContentClipping, bool force) const { if (!fScreenClippingValid || force) { fScreenClipping = fLocalClipping; ConvertToScreen(&fScreenClipping); // see if parts of our bounds are hidden underneath // the parent, the local clipping does not account for this IntRect clippedBounds = Bounds(); ConvertToVisibleInTopView(&clippedBounds); if (clippedBounds.Width() < fScreenClipping.Frame().Width() || clippedBounds.Height() < fScreenClipping.Frame().Height()) { BRegion* temp = fWindow->GetRegion(); if (temp) { temp->Set((clipping_rect)clippedBounds); fScreenClipping.IntersectWith(temp); fWindow->RecycleRegion(temp); } } fScreenClipping.IntersectWith(windowContentClipping); fScreenClippingValid = true; } return fScreenClipping; } void View::_MoveScreenClipping(int32 x, int32 y, bool deep) { if (fScreenClippingValid) { fScreenClipping.OffsetBy(x, y); delete fScreenAndUserClipping; fScreenAndUserClipping = NULL; } if (deep) { // move the childrens screen clipping as well for (View* child = FirstChild(); child; child = child->NextSibling()) { child->_MoveScreenClipping(x, y, deep); } } }