1 /* 2 * Copyright (c) 2001-2015, Haiku, Inc. 3 * Distributed under the terms of the MIT license. 4 * 5 * Authors: 6 * DarkWyrm <bpmagic@columbus.rr.com> 7 * Adi Oanca <adioanca@gmail.com> 8 * Axel Dörfler, axeld@pinc-software.de 9 * Stephan Aßmus <superstippi@gmx.de> 10 * Marcus Overhagen <marcus@overhagen.de> 11 * Adrien Destugues <pulkomandy@pulkomandy.tk 12 * Julian Harnath <julian.harnath@rwth-aachen.de> 13 * Joseph Groover <looncraz@looncraz.net> 14 */ 15 #include "View.h" 16 17 #include <new> 18 #include <stdio.h> 19 20 #include "AlphaMask.h" 21 #include "Desktop.h" 22 #include "DrawingEngine.h" 23 #include "DrawState.h" 24 #include "Layer.h" 25 #include "Overlay.h" 26 #include "ServerApp.h" 27 #include "ServerBitmap.h" 28 #include "ServerCursor.h" 29 #include "ServerPicture.h" 30 #include "ServerWindow.h" 31 #include "Window.h" 32 33 #include "BitmapHWInterface.h" 34 #include "drawing_support.h" 35 36 #include <List.h> 37 #include <Message.h> 38 #include <PortLink.h> 39 #include <View.h> // for resize modes 40 #include <WindowPrivate.h> 41 42 #include <GradientLinear.h> 43 #include <GradientRadial.h> 44 #include <GradientRadialFocus.h> 45 #include <GradientDiamond.h> 46 #include <GradientConic.h> 47 48 49 using std::nothrow; 50 51 52 void 53 resize_frame(IntRect& frame, uint32 resizingMode, int32 x, int32 y) 54 { 55 // follow with left side 56 if ((resizingMode & 0x0F00U) == _VIEW_RIGHT_ << 8) 57 frame.left += x; 58 else if ((resizingMode & 0x0F00U) == _VIEW_CENTER_ << 8) 59 frame.left += x / 2; 60 61 // follow with right side 62 if ((resizingMode & 0x000FU) == _VIEW_RIGHT_) 63 frame.right += x; 64 else if ((resizingMode & 0x000FU) == _VIEW_CENTER_) 65 frame.right += x / 2; 66 67 // follow with top side 68 if ((resizingMode & 0xF000U) == _VIEW_BOTTOM_ << 12) 69 frame.top += y; 70 else if ((resizingMode & 0xF000U) == _VIEW_CENTER_ << 12) 71 frame.top += y / 2; 72 73 // follow with bottom side 74 if ((resizingMode & 0x00F0U) == _VIEW_BOTTOM_ << 4) 75 frame.bottom += y; 76 else if ((resizingMode & 0x00F0U) == _VIEW_CENTER_ << 4) 77 frame.bottom += y / 2; 78 } 79 80 81 // #pragma mark - 82 83 84 View::View(IntRect frame, IntPoint scrollingOffset, const char* name, 85 int32 token, uint32 resizeMode, uint32 flags) 86 : 87 fName(name), 88 fToken(token), 89 90 fFrame(frame), 91 fScrollingOffset(scrollingOffset), 92 93 fViewColor((rgb_color){ 255, 255, 255, 255 }), 94 fWhichViewColor(B_NO_COLOR), 95 fWhichViewColorTint(B_NO_TINT), 96 fViewBitmap(NULL), 97 fBitmapResizingMode(0), 98 fBitmapOptions(0), 99 100 fResizeMode(resizeMode), 101 fFlags(flags), 102 103 // Views start visible by default 104 fHidden(false), 105 fVisible(true), 106 fBackgroundDirty(true), 107 fIsDesktopBackground(false), 108 109 fEventMask(0), 110 fEventOptions(0), 111 112 fWindow(NULL), 113 fParent(NULL), 114 115 fFirstChild(NULL), 116 fPreviousSibling(NULL), 117 fNextSibling(NULL), 118 fLastChild(NULL), 119 120 fCursor(NULL), 121 fPicture(NULL), 122 123 fLocalClipping((BRect)Bounds()), 124 fScreenClipping(), 125 fScreenClippingValid(false), 126 fUserClipping(NULL), 127 fScreenAndUserClipping(NULL) 128 { 129 if (fDrawState.IsSet()) 130 fDrawState->SetSubPixelPrecise(fFlags & B_SUBPIXEL_PRECISE); 131 } 132 133 134 View::~View() 135 { 136 // iterate over children and delete each one 137 View* view = fFirstChild; 138 while (view) { 139 View* toast = view; 140 view = view->fNextSibling; 141 delete toast; 142 } 143 } 144 145 146 IntRect 147 View::Bounds() const 148 { 149 IntRect bounds(fScrollingOffset.x, fScrollingOffset.y, 150 fScrollingOffset.x + fFrame.Width(), 151 fScrollingOffset.y + fFrame.Height()); 152 return bounds; 153 } 154 155 156 void 157 View::ConvertToVisibleInTopView(IntRect* bounds) const 158 { 159 *bounds = *bounds & Bounds(); 160 // NOTE: this step is necessary even if we don't have a parent! 161 bounds->OffsetBy(fFrame.left - fScrollingOffset.x, 162 fFrame.top - fScrollingOffset.y); 163 164 if (fParent) 165 fParent->ConvertToVisibleInTopView(bounds); 166 } 167 168 169 void 170 View::AttachedToWindow(::Window* window) 171 { 172 fWindow = window; 173 174 // an ugly hack to detect the desktop background 175 if (window->Feel() == kDesktopWindowFeel && Parent() == TopView()) 176 fIsDesktopBackground = true; 177 178 // insert view into local token space 179 if (fWindow != NULL) { 180 fWindow->ServerWindow()->App()->ViewTokens().SetToken(fToken, 181 B_HANDLER_TOKEN, this); 182 } 183 184 // attach child views as well 185 for (View* child = FirstChild(); child; child = child->NextSibling()) 186 child->AttachedToWindow(window); 187 } 188 189 190 void 191 View::DetachedFromWindow() 192 { 193 // remove view from local token space 194 if (fWindow != NULL && fWindow->ServerWindow()->App() != NULL) 195 fWindow->ServerWindow()->App()->ViewTokens().RemoveToken(fToken); 196 197 fWindow = NULL; 198 // detach child views as well 199 for (View* child = FirstChild(); child; child = child->NextSibling()) 200 child->DetachedFromWindow(); 201 } 202 203 204 // #pragma mark - 205 206 207 DrawingEngine* 208 View::GetDrawingEngine() const 209 { 210 return Window()->GetDrawingEngine(); 211 } 212 213 214 ServerPicture* 215 View::GetPicture(int32 token) const 216 { 217 return Window()->ServerWindow()->App()->GetPicture(token); 218 } 219 220 221 void 222 View::ResyncDrawState() 223 { 224 return Window()->ServerWindow()->ResyncDrawState(); 225 } 226 227 228 void 229 View::UpdateCurrentDrawingRegion() 230 { 231 return Window()->ServerWindow()->UpdateCurrentDrawingRegion(); 232 } 233 234 235 void 236 View::AddChild(View* view) 237 { 238 if (view->fParent) { 239 printf("View::AddChild() - View already has a parent\n"); 240 return; 241 } 242 243 view->fParent = this; 244 245 if (!fLastChild) { 246 // no children yet 247 fFirstChild = view; 248 } else { 249 // append view to formerly last child 250 fLastChild->fNextSibling = view; 251 view->fPreviousSibling = fLastChild; 252 } 253 fLastChild = view; 254 255 view->UpdateVisibleDeep(fVisible); 256 257 if (view->IsVisible()) 258 RebuildClipping(false); 259 260 if (fWindow) { 261 view->AttachedToWindow(fWindow); 262 263 if (view->IsVisible()) { 264 // trigger redraw 265 IntRect clippedFrame = view->Frame(); 266 ConvertToVisibleInTopView(&clippedFrame); 267 268 BRegion dirty; 269 dirty.Set((clipping_rect)clippedFrame); 270 fWindow->MarkContentDirtyAsync(dirty); 271 } 272 } 273 } 274 275 276 bool 277 View::RemoveChild(View* view) 278 { 279 if (view == NULL || view->fParent != this) { 280 printf("View::RemoveChild(%p - %s) - View is not child of " 281 "this (%p) view!\n", view, view ? view->Name() : NULL, this); 282 return false; 283 } 284 285 view->fParent = NULL; 286 287 if (fLastChild == view) 288 fLastChild = view->fPreviousSibling; 289 // view->fNextSibling would be NULL 290 291 if (fFirstChild == view ) 292 fFirstChild = view->fNextSibling; 293 // view->fPreviousSibling would be NULL 294 295 // connect child before and after view 296 if (view->fPreviousSibling) 297 view->fPreviousSibling->fNextSibling = view->fNextSibling; 298 299 if (view->fNextSibling) 300 view->fNextSibling->fPreviousSibling = view->fPreviousSibling; 301 302 // view has no siblings anymore 303 view->fPreviousSibling = NULL; 304 view->fNextSibling = NULL; 305 306 if (view->IsVisible()) { 307 Overlay* overlay = view->_Overlay(); 308 if (overlay != NULL) 309 overlay->Hide(); 310 311 RebuildClipping(false); 312 } 313 314 if (fWindow) { 315 view->DetachedFromWindow(); 316 317 if (fVisible && view->IsVisible()) { 318 // trigger redraw 319 IntRect clippedFrame = view->Frame(); 320 ConvertToVisibleInTopView(&clippedFrame); 321 322 BRegion dirty; 323 dirty.Set((clipping_rect)clippedFrame); 324 fWindow->MarkContentDirtyAsync(dirty); 325 } 326 } 327 328 return true; 329 } 330 331 332 View* 333 View::TopView() 334 { 335 // returns the top level view of the hirarchy, 336 // it doesn't have to be the top level of a window 337 338 if (fParent) 339 return fParent->TopView(); 340 341 return this; 342 } 343 344 345 uint32 346 View::CountChildren(bool deep) const 347 { 348 uint32 count = 0; 349 for (View* child = FirstChild(); child; child = child->NextSibling()) { 350 count++; 351 if (deep) { 352 count += child->CountChildren(deep); 353 } 354 } 355 return count; 356 } 357 358 359 void 360 View::CollectTokensForChildren(BList* tokenMap) const 361 { 362 for (View* child = FirstChild(); child; child = child->NextSibling()) { 363 tokenMap->AddItem((void*)child); 364 child->CollectTokensForChildren(tokenMap); 365 } 366 } 367 368 369 #if 0 370 bool 371 View::MarkAt(DrawingEngine* engine, const BPoint& where, int32 level) 372 { 373 BRect rect(fFrame.left, fFrame.top, fFrame.right, fFrame.bottom); 374 375 if (Parent() != NULL) { 376 Parent()->ConvertToScreen(&rect); 377 if (!rect.Contains(where)) 378 return false; 379 380 engine->StrokeRect(rect, (rgb_color){level * 30, level * 30, level * 30}); 381 } 382 383 384 bool found = false; 385 for (View* child = FirstChild(); child; child = child->NextSibling()) { 386 found |= child->MarkAt(engine, where, level + 1); 387 } 388 389 if (!found) { 390 rgb_color color = {0}; 391 switch (level % 2) { 392 case 0: color.green = rand() % 256; break; 393 case 1: color.blue = rand() % 256; break; 394 } 395 396 rect.InsetBy(1, 1); 397 //engine->FillRegion(fLocalClipping, (rgb_color){255, 255, 0, 10}); 398 engine->StrokeRect(rect, color); 399 rect.InsetBy(1, 1); 400 engine->StrokeRect(rect, color); 401 } 402 403 return true; 404 } 405 #endif 406 407 408 void 409 View::FindViews(uint32 flags, BObjectList<View>& list, int32& left) 410 { 411 if ((Flags() & flags) == flags) { 412 list.AddItem(this); 413 left--; 414 return; 415 } 416 417 for (View* child = FirstChild(); child; child = child->NextSibling()) { 418 child->FindViews(flags, list, left); 419 if (left == 0) 420 break; 421 } 422 } 423 424 425 bool 426 View::HasView(View* view) 427 { 428 if (view == this) 429 return true; 430 431 for (View* child = FirstChild(); child; child = child->NextSibling()) { 432 if (child->HasView(view)) 433 return true; 434 } 435 436 return false; 437 } 438 439 440 View* 441 View::ViewAt(const BPoint& where) 442 { 443 if (!fVisible) 444 return NULL; 445 446 IntRect frame = Frame(); 447 if (Parent() != NULL) 448 Parent()->LocalToScreenTransform().Apply(&frame); 449 450 if (!frame.Contains(where)) 451 return NULL; 452 453 for (View* child = FirstChild(); child; child = child->NextSibling()) { 454 View* view = child->ViewAt(where); 455 if (view != NULL) 456 return view; 457 } 458 459 return this; 460 } 461 462 463 // #pragma mark - 464 465 466 void 467 View::SetName(const char* string) 468 { 469 fName.SetTo(string); 470 } 471 472 473 void 474 View::SetFlags(uint32 flags) 475 { 476 uint32 oldFlags = fFlags; 477 fFlags = flags; 478 479 // Child view with B_TRANSPARENT_BACKGROUND flag change clipping of 480 // parent view. 481 if (fParent != NULL 482 && IsVisible() 483 && (((oldFlags & B_TRANSPARENT_BACKGROUND) != 0) 484 != ((fFlags & B_TRANSPARENT_BACKGROUND) != 0))) { 485 fParent->RebuildClipping(false); 486 } 487 488 fDrawState->SetSubPixelPrecise(fFlags & B_SUBPIXEL_PRECISE); 489 } 490 491 492 void 493 View::SetViewBitmap(ServerBitmap* bitmap, IntRect sourceRect, 494 IntRect destRect, int32 resizingMode, int32 options) 495 { 496 if (fViewBitmap != NULL) { 497 Overlay* overlay = _Overlay(); 498 499 if (bitmap != NULL) { 500 // take over overlay token from current overlay (if it has any) 501 Overlay* newOverlay = bitmap->Overlay(); 502 503 if (overlay != NULL && newOverlay != NULL) 504 newOverlay->TakeOverToken(overlay); 505 } else if (overlay != NULL) 506 overlay->Hide(); 507 } 508 509 fViewBitmap.SetTo(bitmap, false); 510 fBitmapSource = sourceRect; 511 fBitmapDestination = destRect; 512 fBitmapResizingMode = resizingMode; 513 fBitmapOptions = options; 514 515 _UpdateOverlayView(); 516 } 517 518 519 ::Overlay* 520 View::_Overlay() const 521 { 522 if (fViewBitmap == NULL) 523 return NULL; 524 525 return fViewBitmap->Overlay(); 526 } 527 528 529 void 530 View::_UpdateOverlayView() const 531 { 532 Overlay* overlay = _Overlay(); 533 if (overlay == NULL) 534 return; 535 536 IntRect destination = fBitmapDestination; 537 LocalToScreenTransform().Apply(&destination); 538 539 overlay->Configure(fBitmapSource, destination); 540 } 541 542 543 /*! 544 This method is called whenever the window is resized or moved - would 545 be nice to have a better solution for this, though. 546 */ 547 void 548 View::UpdateOverlay() 549 { 550 if (!IsVisible()) 551 return; 552 553 if (_Overlay() != NULL) { 554 _UpdateOverlayView(); 555 } else { 556 // recursively ask children of this view 557 558 for (View* child = FirstChild(); child; child = child->NextSibling()) { 559 child->UpdateOverlay(); 560 } 561 } 562 } 563 564 565 // #pragma mark - 566 567 568 void 569 View::_LocalToScreenTransform(SimpleTransform& transform) const 570 { 571 const View* view = this; 572 int32 offsetX = 0; 573 int32 offsetY = 0; 574 do { 575 offsetX += view->fFrame.left - view->fScrollingOffset.x; 576 offsetY += view->fFrame.top - view->fScrollingOffset.y; 577 view = view->fParent; 578 } while (view != NULL); 579 580 transform.AddOffset(offsetX, offsetY); 581 } 582 583 584 void 585 View::_ScreenToLocalTransform(SimpleTransform& transform) const 586 { 587 const View* view = this; 588 int32 offsetX = 0; 589 int32 offsetY = 0; 590 do { 591 offsetX += view->fScrollingOffset.x - view->fFrame.left; 592 offsetY += view->fScrollingOffset.y - view->fFrame.top; 593 view = view->fParent; 594 } while (view != NULL); 595 596 transform.AddOffset(offsetX, offsetY); 597 } 598 599 600 // #pragma mark - 601 602 603 void 604 View::MoveBy(int32 x, int32 y, BRegion* dirtyRegion) 605 { 606 if (x == 0 && y == 0) 607 return; 608 609 fFrame.OffsetBy(x, y); 610 611 // to move on screen, we must not be hidden and we must have a parent 612 if (fVisible && fParent && dirtyRegion) { 613 #if 1 614 // based on redraw on new location 615 // the place were we are now visible 616 IntRect newVisibleBounds(Bounds()); 617 // we can use the frame of the old 618 // local clipping to see which parts need invalidation 619 IntRect oldVisibleBounds(newVisibleBounds); 620 oldVisibleBounds.OffsetBy(-x, -y); 621 LocalToScreenTransform().Apply(&oldVisibleBounds); 622 623 ConvertToVisibleInTopView(&newVisibleBounds); 624 625 dirtyRegion->Include((clipping_rect)oldVisibleBounds); 626 // newVisibleBounds already is in screen coords 627 dirtyRegion->Include((clipping_rect)newVisibleBounds); 628 #else 629 // blitting version, invalidates 630 // old contents 631 IntRect oldVisibleBounds(Bounds()); 632 IntRect newVisibleBounds(oldVisibleBounds); 633 oldVisibleBounds.OffsetBy(-x, -y); 634 LocalToScreenTransform().Apply(&oldVisibleBounds); 635 636 // NOTE: using ConvertToVisibleInTopView() 637 // instead of ConvertToScreen()! see below 638 ConvertToVisibleInTopView(&newVisibleBounds); 639 640 newVisibleBounds.OffsetBy(-x, -y); 641 642 // clipping oldVisibleBounds to newVisibleBounds 643 // makes sure we don't copy parts hidden under 644 // parent views 645 BRegion* region = fWindow->GetRegion(); 646 if (region) { 647 region->Set(oldVisibleBounds & newVisibleBounds); 648 fWindow->CopyContents(region, x, y); 649 650 region->Set(oldVisibleBounds); 651 newVisibleBounds.OffsetBy(x, y); 652 region->Exclude((clipping_rect)newVisibleBounds); 653 dirtyRegion->Include(dirty); 654 655 fWindow->RecycleRegion(region); 656 } 657 658 #endif 659 } 660 661 if (!fParent) { 662 // the top view's screen clipping does not change, 663 // because no parts are clipped away from parent 664 // views 665 _MoveScreenClipping(x, y, true); 666 } else { 667 // parts might have been revealed from underneath 668 // the parent, or might now be hidden underneath 669 // the parent, this is taken care of when building 670 // the screen clipping 671 InvalidateScreenClipping(); 672 } 673 } 674 675 676 void 677 View::ResizeBy(int32 x, int32 y, BRegion* dirtyRegion) 678 { 679 if (x == 0 && y == 0) 680 return; 681 682 fFrame.right += x; 683 fFrame.bottom += y; 684 685 if (fVisible && dirtyRegion) { 686 IntRect oldBounds(Bounds()); 687 oldBounds.right -= x; 688 oldBounds.bottom -= y; 689 690 BRegion* dirty = fWindow->GetRegion(); 691 if (!dirty) 692 return; 693 694 dirty->Set((clipping_rect)Bounds()); 695 dirty->Include((clipping_rect)oldBounds); 696 697 if (!(fFlags & B_FULL_UPDATE_ON_RESIZE)) { 698 // the dirty region is just the difference of 699 // old and new bounds 700 dirty->Exclude((clipping_rect)(oldBounds & Bounds())); 701 } 702 703 InvalidateScreenClipping(); 704 705 if (dirty->CountRects() > 0) { 706 if ((fFlags & B_DRAW_ON_CHILDREN) == 0) { 707 // exclude children, they are expected to 708 // include their own dirty regions in ParentResized() 709 for (View* child = FirstChild(); child; 710 child = child->NextSibling()) { 711 if (!child->IsVisible() 712 || (child->fFlags & B_TRANSPARENT_BACKGROUND) != 0) { 713 continue; 714 } 715 IntRect previousChildVisible( 716 child->Frame() & oldBounds & Bounds()); 717 if (dirty->Frame().Intersects(previousChildVisible)) { 718 dirty->Exclude((clipping_rect)previousChildVisible); 719 } 720 } 721 } 722 723 LocalToScreenTransform().Apply(dirty); 724 dirtyRegion->Include(dirty); 725 } 726 fWindow->RecycleRegion(dirty); 727 } 728 729 // layout the children 730 for (View* child = FirstChild(); child; child = child->NextSibling()) 731 child->ParentResized(x, y, dirtyRegion); 732 733 // view bitmap 734 if (fViewBitmap != NULL) 735 resize_frame(fBitmapDestination, fBitmapResizingMode, x, y); 736 737 // at this point, children are at their new locations, 738 // so we can rebuild the clipping 739 // TODO: when the implementation of Hide() and Show() is 740 // complete, see if this should be avoided 741 RebuildClipping(false); 742 } 743 744 745 void 746 View::ParentResized(int32 x, int32 y, BRegion* dirtyRegion) 747 { 748 IntRect newFrame = fFrame; 749 resize_frame(newFrame, fResizeMode & 0x0000ffff, x, y); 750 751 if (newFrame != fFrame) { 752 // careful, MoveBy will change fFrame 753 int32 widthDiff = (int32)(newFrame.Width() - fFrame.Width()); 754 int32 heightDiff = (int32)(newFrame.Height() - fFrame.Height()); 755 756 MoveBy(newFrame.left - fFrame.left, 757 newFrame.top - fFrame.top, dirtyRegion); 758 759 ResizeBy(widthDiff, heightDiff, dirtyRegion); 760 } else { 761 // TODO: this covers the fact that our screen clipping might change 762 // when the parent changes its size, even though our frame stays 763 // the same - there might be a way to test for this, but axeld doesn't 764 // know, stippi should look into this when he's back :) 765 InvalidateScreenClipping(); 766 } 767 } 768 769 770 void 771 View::ScrollBy(int32 x, int32 y, BRegion* dirtyRegion) 772 { 773 if (!fVisible || !fWindow) { 774 fScrollingOffset.x += x; 775 fScrollingOffset.y += y; 776 return; 777 } 778 779 // blitting version, invalidates 780 // old contents 781 782 // remember old bounds for tracking dirty region 783 IntRect oldBounds(Bounds()); 784 785 // NOTE: using ConvertToVisibleInTopView() 786 // instead of ConvertToScreen(), this makes 787 // sure we don't try to move or invalidate an 788 // area hidden underneath the parent view 789 ConvertToVisibleInTopView(&oldBounds); 790 791 // find the area of the view that can be scrolled, 792 // contents are shifted in the opposite direction from scrolling 793 IntRect stillVisibleBounds(oldBounds); 794 stillVisibleBounds.OffsetBy(x, y); 795 stillVisibleBounds = stillVisibleBounds & oldBounds; 796 797 fScrollingOffset.x += x; 798 fScrollingOffset.y += y; 799 800 // do the blit, this will make sure 801 // that other more complex dirty regions 802 // are taken care of 803 BRegion* copyRegion = fWindow->GetRegion(); 804 if (!copyRegion) 805 return; 806 copyRegion->Set((clipping_rect)stillVisibleBounds); 807 fWindow->CopyContents(copyRegion, -x, -y); 808 809 // find the dirty region as far as we are 810 // concerned 811 BRegion* dirty = copyRegion; 812 // reuse copyRegion and call it dirty 813 814 dirty->Set((clipping_rect)oldBounds); 815 stillVisibleBounds.OffsetBy(-x, -y); 816 dirty->Exclude((clipping_rect)stillVisibleBounds); 817 dirtyRegion->Include(dirty); 818 819 fWindow->RecycleRegion(dirty); 820 821 // the screen clipping of this view and it's 822 // childs is no longer valid 823 InvalidateScreenClipping(); 824 RebuildClipping(false); 825 } 826 827 828 void 829 View::CopyBits(IntRect src, IntRect dst, BRegion& windowContentClipping) 830 { 831 if (!fVisible || !fWindow) 832 return; 833 834 // TODO: figure out what to do when we have a transform which is not 835 // a dilation 836 BAffineTransform transform = CurrentState()->CombinedTransform(); 837 if (!transform.IsIdentity() && transform.IsDilation()) { 838 BPoint points[4] = { src.LeftTop(), src.RightBottom(), 839 dst.LeftTop(), dst.RightBottom() }; 840 transform.Apply(&points[0], 4); 841 src.Set(points[0].x, points[0].y, points[1].x, points[1].y); 842 dst.Set(points[2].x, points[2].y, points[3].x, points[3].y); 843 } 844 845 // TODO: confirm that in R5 this call is affected by origin and scale 846 847 // blitting version 848 849 int32 xOffset = dst.left - src.left; 850 int32 yOffset = dst.top - src.top; 851 852 // figure out which part can be blittet 853 IntRect visibleSrc(src); 854 ConvertToVisibleInTopView(&visibleSrc); 855 856 IntRect visibleSrcAtDest(src); 857 visibleSrcAtDest.OffsetBy(xOffset, yOffset); 858 ConvertToVisibleInTopView(&visibleSrcAtDest); 859 860 // clip src to visible at dest 861 visibleSrcAtDest.OffsetBy(-xOffset, -yOffset); 862 visibleSrc = visibleSrc & visibleSrcAtDest; 863 864 // do the blit, this will make sure 865 // that other more complex dirty regions 866 // are taken care of 867 BRegion* copyRegion = fWindow->GetRegion(); 868 if (!copyRegion) 869 return; 870 871 // move src rect to destination here for efficiency reasons 872 visibleSrc.OffsetBy(xOffset, yOffset); 873 874 // we need to interstect the copyRegion two times, onces 875 // at the source and once at the destination (here done 876 // the other way arround but it doesn't matter) 877 // the reason for this is that we are not supposed to visually 878 // copy children in the source rect and neither to copy onto 879 // children in the destination rect... 880 copyRegion->Set((clipping_rect)visibleSrc); 881 BRegion *screenAndUserClipping 882 = &ScreenAndUserClipping(&windowContentClipping); 883 copyRegion->IntersectWith(screenAndUserClipping); 884 copyRegion->OffsetBy(-xOffset, -yOffset); 885 copyRegion->IntersectWith(screenAndUserClipping); 886 887 // do the actual blit 888 fWindow->CopyContents(copyRegion, xOffset, yOffset); 889 890 // find the dirty region as far as we are concerned 891 IntRect dirtyDst(dst); 892 ConvertToVisibleInTopView(&dirtyDst); 893 894 BRegion* dirty = fWindow->GetRegion(); 895 if (!dirty) { 896 fWindow->RecycleRegion(copyRegion); 897 return; 898 } 899 900 // offset copyRegion to destination again 901 copyRegion->OffsetBy(xOffset, yOffset); 902 // start with destination given by user 903 dirty->Set((clipping_rect)dirtyDst); 904 // exclude the part that we could copy 905 dirty->Exclude(copyRegion); 906 907 dirty->IntersectWith(screenAndUserClipping); 908 fWindow->MarkContentDirty(*dirty, *dirty); 909 910 fWindow->RecycleRegion(dirty); 911 fWindow->RecycleRegion(copyRegion); 912 } 913 914 915 // #pragma mark - 916 917 918 void 919 View::ColorUpdated(color_which which, rgb_color color) 920 { 921 float tint = B_NO_TINT; 922 923 if (fWhichViewColor == which) 924 SetViewColor(tint_color(color, fWhichViewColorTint)); 925 926 if (CurrentState()->HighUIColor(&tint) == which) 927 CurrentState()->SetHighColor(tint_color(color, tint)); 928 929 if (CurrentState()->LowUIColor(&tint) == which) 930 CurrentState()->SetLowColor(tint_color(color, tint)); 931 932 for (View* child = FirstChild(); child != NULL; 933 child = child->NextSibling()) { 934 935 child->ColorUpdated(which, color); 936 } 937 } 938 939 940 void 941 View::SetViewUIColor(color_which which, float tint) 942 { 943 if (which != B_NO_COLOR) { 944 DesktopSettings settings(fWindow->Desktop()); 945 SetViewColor(tint_color(settings.UIColor(which), tint)); 946 } 947 948 fWhichViewColor = which; 949 fWhichViewColorTint = tint; 950 } 951 952 953 color_which 954 View::ViewUIColor(float* tint) 955 { 956 if (tint != NULL) 957 *tint = fWhichViewColorTint; 958 959 return fWhichViewColor; 960 } 961 962 963 // #pragma mark - 964 965 966 void 967 View::PushState() 968 { 969 DrawState* previousState = fDrawState.Detach(); 970 DrawState* newState = previousState->PushState(); 971 if (newState == NULL) 972 newState = previousState; 973 974 fDrawState.SetTo(newState); 975 // In BeAPI, B_SUBPIXEL_PRECISE is a view flag, and not affected by the 976 // view state. Our implementation moves it to the draw state, but let's 977 // be compatible with the API here and make it survive accross state 978 // changes. 979 fDrawState->SetSubPixelPrecise(fFlags & B_SUBPIXEL_PRECISE); 980 } 981 982 983 void 984 View::PopState() 985 { 986 if (fDrawState->PreviousState() == NULL) { 987 fprintf(stderr, "WARNING: User called BView(%s)::PopState(), " 988 "but there is NO state on stack!\n", Name()); 989 return; 990 } 991 992 bool rebuildClipping = fDrawState->HasAdditionalClipping(); 993 994 fDrawState.SetTo(fDrawState->PopState()); 995 fDrawState->SetSubPixelPrecise(fFlags & B_SUBPIXEL_PRECISE); 996 997 // rebuild clipping 998 // (the clipping from the popped state is not effective anymore) 999 if (rebuildClipping) 1000 RebuildClipping(false); 1001 } 1002 1003 1004 // #pragma mark - 1005 1006 1007 void 1008 View::SetEventMask(uint32 eventMask, uint32 options) 1009 { 1010 fEventMask = eventMask; 1011 fEventOptions = options; 1012 } 1013 1014 1015 void 1016 View::SetCursor(ServerCursor* cursor) 1017 { 1018 if (cursor == fCursor) 1019 return; 1020 1021 fCursor.SetTo(cursor, false); 1022 } 1023 1024 1025 void 1026 View::SetPicture(ServerPicture* picture) 1027 { 1028 if (picture == fPicture) 1029 return; 1030 1031 fPicture.SetTo(picture, false); 1032 } 1033 1034 1035 void 1036 View::BlendAllLayers() 1037 { 1038 if (fPicture == NULL) 1039 return; 1040 Layer* layer = dynamic_cast<Layer*>(fPicture.Get()); 1041 if (layer == NULL) 1042 return; 1043 BlendLayer(layer); 1044 } 1045 1046 1047 void 1048 View::Draw(DrawingEngine* drawingEngine, const BRegion* effectiveClipping, 1049 const BRegion* windowContentClipping, bool deep) 1050 { 1051 if (!fVisible) { 1052 // child views cannot be visible either 1053 return; 1054 } 1055 1056 if (fViewBitmap != NULL || fViewColor != B_TRANSPARENT_COLOR) { 1057 // we can only draw within our own area 1058 BRegion* redraw; 1059 if ((fFlags & B_DRAW_ON_CHILDREN) != 0) { 1060 // The client may actually want to prevent the background to 1061 // be painted outside the user clipping. 1062 redraw = fWindow->GetRegion( 1063 ScreenAndUserClipping(windowContentClipping)); 1064 } else { 1065 // Ignore user clipping as in BeOS for painting the background. 1066 redraw = fWindow->GetRegion( 1067 _ScreenClipping(windowContentClipping)); 1068 } 1069 if (!redraw) 1070 return; 1071 // add the current clipping 1072 redraw->IntersectWith(effectiveClipping); 1073 1074 Overlay* overlayCookie = _Overlay(); 1075 1076 if (fViewBitmap != NULL && overlayCookie == NULL) { 1077 // draw view bitmap 1078 // TODO: support other options! 1079 BRect rect = fBitmapDestination; 1080 PenToScreenTransform().Apply(&rect); 1081 1082 align_rect_to_pixels(&rect); 1083 1084 if (fBitmapOptions & B_TILE_BITMAP_Y) { 1085 // move rect up as much as needed 1086 while (rect.top > redraw->Frame().top) 1087 rect.OffsetBy(0.0, -(rect.Height() + 1)); 1088 } 1089 if (fBitmapOptions & B_TILE_BITMAP_X) { 1090 // move rect left as much as needed 1091 while (rect.left > redraw->Frame().left) 1092 rect.OffsetBy(-(rect.Width() + 1), 0.0); 1093 } 1094 1095 // XXX: locking removed because the Window keeps the engine locked 1096 // because it keeps track of syncing right now 1097 1098 // lock the drawing engine for as long as we need the clipping 1099 // to be valid 1100 if (rect.IsValid()/* && drawingEngine->Lock()*/) { 1101 drawingEngine->ConstrainClippingRegion(redraw); 1102 1103 drawing_mode oldMode; 1104 drawingEngine->SetDrawingMode(B_OP_COPY, oldMode); 1105 1106 if (fBitmapOptions & B_TILE_BITMAP) { 1107 // tile across entire view 1108 1109 float start = rect.left; 1110 while (rect.top < redraw->Frame().bottom) { 1111 while (rect.left < redraw->Frame().right) { 1112 drawingEngine->DrawBitmap(fViewBitmap, 1113 fBitmapSource, rect, fBitmapOptions); 1114 rect.OffsetBy(rect.Width() + 1, 0.0); 1115 } 1116 rect.OffsetBy(start - rect.left, rect.Height() + 1); 1117 } 1118 // nothing left to be drawn 1119 redraw->MakeEmpty(); 1120 } else if (fBitmapOptions & B_TILE_BITMAP_X) { 1121 // tile in x direction 1122 1123 while (rect.left < redraw->Frame().right) { 1124 drawingEngine->DrawBitmap(fViewBitmap, fBitmapSource, 1125 rect, fBitmapOptions); 1126 rect.OffsetBy(rect.Width() + 1, 0.0); 1127 } 1128 // remove horizontal stripe from clipping 1129 rect.left = redraw->Frame().left; 1130 rect.right = redraw->Frame().right; 1131 redraw->Exclude(rect); 1132 } else if (fBitmapOptions & B_TILE_BITMAP_Y) { 1133 // tile in y direction 1134 1135 while (rect.top < redraw->Frame().bottom) { 1136 drawingEngine->DrawBitmap(fViewBitmap, fBitmapSource, 1137 rect, fBitmapOptions); 1138 rect.OffsetBy(0.0, rect.Height() + 1); 1139 } 1140 // remove vertical stripe from clipping 1141 rect.top = redraw->Frame().top; 1142 rect.bottom = redraw->Frame().bottom; 1143 redraw->Exclude(rect); 1144 } else { 1145 // no tiling at all 1146 1147 drawingEngine->DrawBitmap(fViewBitmap, fBitmapSource, 1148 rect, fBitmapOptions); 1149 redraw->Exclude(rect); 1150 } 1151 1152 drawingEngine->SetDrawingMode(oldMode); 1153 1154 // NOTE: It is ok not to reset the clipping, that 1155 // would only waste time 1156 // drawingEngine->Unlock(); 1157 } 1158 1159 } 1160 1161 if (fViewColor != B_TRANSPARENT_COLOR) { 1162 // fill visible region with view color, 1163 // this version of FillRegion ignores any 1164 // clipping, that's why "redraw" needs to 1165 // be correct 1166 // see #634 1167 // if (redraw->Frame().left < 0 || redraw->Frame().top < 0) { 1168 // char message[1024]; 1169 // BRect c = effectiveClipping->Frame(); 1170 // BRect w = windowContentClipping->Frame(); 1171 // BRect r = redraw->Frame(); 1172 // sprintf(message, "invalid background: current clipping: (%d, %d)->(%d, %d), " 1173 // "window content: (%d, %d)->(%d, %d), redraw: (%d, %d)->(%d, %d)", 1174 // (int)c.left, (int)c.top, (int)c.right, (int)c.bottom, 1175 // (int)w.left, (int)w.top, (int)w.right, (int)w.bottom, 1176 // (int)r.left, (int)r.top, (int)r.right, (int)r.bottom); 1177 // debugger(message); 1178 // } 1179 1180 drawingEngine->FillRegion(*redraw, overlayCookie != NULL 1181 ? overlayCookie->Color() : fViewColor); 1182 } 1183 1184 fWindow->RecycleRegion(redraw); 1185 } 1186 1187 fBackgroundDirty = false; 1188 1189 // let children draw 1190 if (deep) { 1191 for (View* child = FirstChild(); child; child = child->NextSibling()) { 1192 child->Draw(drawingEngine, effectiveClipping, 1193 windowContentClipping, deep); 1194 } 1195 } 1196 } 1197 1198 1199 // #pragma mark - 1200 1201 1202 void 1203 View::MouseDown(BMessage* message, BPoint where) 1204 { 1205 // empty hook method 1206 } 1207 1208 1209 void 1210 View::MouseUp(BMessage* message, BPoint where) 1211 { 1212 // empty hook method 1213 } 1214 1215 1216 void 1217 View::MouseMoved(BMessage* message, BPoint where) 1218 { 1219 // empty hook method 1220 } 1221 1222 1223 // #pragma mark - 1224 1225 1226 void 1227 View::SetHidden(bool hidden) 1228 { 1229 if (fHidden != hidden) { 1230 fHidden = hidden; 1231 1232 // recurse into children and update their visible flag 1233 bool oldVisible = fVisible; 1234 UpdateVisibleDeep(fParent ? fParent->IsVisible() : !fHidden); 1235 if (oldVisible != fVisible) { 1236 // Include or exclude us from the parent area, and update the 1237 // children's clipping as well when the view will be visible 1238 if (fParent) 1239 fParent->RebuildClipping(fVisible); 1240 else 1241 RebuildClipping(fVisible); 1242 1243 if (fWindow) { 1244 // trigger a redraw 1245 IntRect clippedBounds = Bounds(); 1246 ConvertToVisibleInTopView(&clippedBounds); 1247 1248 BRegion dirty, expose; 1249 dirty.Set((clipping_rect)clippedBounds); 1250 fWindow->MarkContentDirty(dirty, expose); 1251 } 1252 } 1253 } 1254 } 1255 1256 1257 bool 1258 View::IsHidden() const 1259 { 1260 return fHidden; 1261 } 1262 1263 1264 void 1265 View::UpdateVisibleDeep(bool parentVisible) 1266 { 1267 bool wasVisible = fVisible; 1268 1269 fVisible = parentVisible && !fHidden; 1270 for (View* child = FirstChild(); child; child = child->NextSibling()) 1271 child->UpdateVisibleDeep(fVisible); 1272 1273 // overlay handling 1274 1275 Overlay* overlay = _Overlay(); 1276 if (overlay == NULL) 1277 return; 1278 1279 if (fVisible && !wasVisible) 1280 _UpdateOverlayView(); 1281 else if (!fVisible && wasVisible) 1282 overlay->Hide(); 1283 } 1284 1285 1286 // #pragma mark - 1287 1288 1289 void 1290 View::MarkBackgroundDirty() 1291 { 1292 if (fBackgroundDirty) 1293 return; 1294 fBackgroundDirty = true; 1295 for (View* child = FirstChild(); child; child = child->NextSibling()) 1296 child->MarkBackgroundDirty(); 1297 } 1298 1299 1300 void 1301 View::AddTokensForViewsInRegion(BPrivate::PortLink& link, BRegion& region, 1302 BRegion* windowContentClipping) 1303 { 1304 if (!fVisible) 1305 return; 1306 1307 { 1308 // NOTE: use scope in order to reduce stack space requirements 1309 1310 // This check will prevent descending the view hierarchy 1311 // any further than necessary 1312 IntRect screenBounds(Bounds()); 1313 LocalToScreenTransform().Apply(&screenBounds); 1314 if (!region.Intersects((clipping_rect)screenBounds)) 1315 return; 1316 1317 // Unfortunately, we intersecting another region, but otherwise 1318 // we couldn't provide the exact update rect to the client 1319 BRegion localDirty = _ScreenClipping(windowContentClipping); 1320 localDirty.IntersectWith(®ion); 1321 if (localDirty.CountRects() > 0) { 1322 link.Attach<int32>(fToken); 1323 link.Attach<BRect>(localDirty.Frame()); 1324 } 1325 } 1326 1327 for (View* child = FirstChild(); child; child = child->NextSibling()) 1328 child->AddTokensForViewsInRegion(link, region, windowContentClipping); 1329 } 1330 1331 1332 void 1333 View::PrintToStream() const 1334 { 1335 printf("View: %s\n", Name()); 1336 printf(" fToken: %" B_PRId32 "\n", fToken); 1337 printf(" fFrame: IntRect(%" B_PRId32 ", %" B_PRId32 ", %" B_PRId32 ", %" B_PRId32 ")\n", 1338 fFrame.left, fFrame.top, fFrame.right, fFrame.bottom); 1339 printf(" fScrollingOffset: IntPoint(%" B_PRId32 ", %" B_PRId32 ")\n", 1340 fScrollingOffset.x, fScrollingOffset.y); 1341 printf(" fHidden: %d\n", fHidden); 1342 printf(" fVisible: %d\n", fVisible); 1343 printf(" fWindow: %p\n", fWindow); 1344 printf(" fParent: %p\n", fParent); 1345 printf(" fLocalClipping:\n"); 1346 fLocalClipping.PrintToStream(); 1347 printf(" fScreenClipping:\n"); 1348 fScreenClipping.PrintToStream(); 1349 printf(" valid: %d\n", fScreenClippingValid); 1350 1351 printf(" fUserClipping:\n"); 1352 if (fUserClipping.IsSet()) 1353 fUserClipping->PrintToStream(); 1354 else 1355 printf(" none\n"); 1356 1357 printf(" fScreenAndUserClipping:\n"); 1358 if (fScreenAndUserClipping.IsSet()) 1359 fScreenAndUserClipping->PrintToStream(); 1360 else 1361 printf(" invalid\n"); 1362 1363 printf(" state:\n"); 1364 printf(" user clipping: %d\n", fDrawState->HasClipping()); 1365 BPoint origin = fDrawState->CombinedOrigin(); 1366 printf(" origin: BPoint(%.1f, %.1f)\n", origin.x, origin.y); 1367 printf(" scale: %.2f\n", fDrawState->CombinedScale()); 1368 printf("\n"); 1369 } 1370 1371 1372 void 1373 View::RebuildClipping(bool deep) 1374 { 1375 // the clipping spans over the bounds area 1376 fLocalClipping.Set((clipping_rect)Bounds()); 1377 1378 if (View* child = FirstChild()) { 1379 // if this view does not draw over children, 1380 // exclude all children from the clipping 1381 if ((fFlags & B_DRAW_ON_CHILDREN) == 0) { 1382 BRegion* childrenRegion = fWindow->GetRegion(); 1383 if (!childrenRegion) 1384 return; 1385 1386 for (; child; child = child->NextSibling()) { 1387 if (child->IsVisible() 1388 && (child->fFlags & B_TRANSPARENT_BACKGROUND) == 0) { 1389 childrenRegion->Include((clipping_rect)child->Frame()); 1390 } 1391 } 1392 1393 fLocalClipping.Exclude(childrenRegion); 1394 fWindow->RecycleRegion(childrenRegion); 1395 } 1396 // if the operation is "deep", make children rebuild their 1397 // clipping too 1398 if (deep) { 1399 for (child = FirstChild(); child; child = child->NextSibling()) 1400 child->RebuildClipping(true); 1401 } 1402 } 1403 1404 // add the user clipping in case there is one 1405 if (fDrawState->HasClipping()) { 1406 // NOTE: in case the user sets a user defined clipping region, 1407 // rebuilding the clipping is a bit more expensive because there 1408 // is no separate "drawing region"... on the other 1409 // hand, views for which this feature is actually used will 1410 // probably not have any children, so it is not that expensive 1411 // after all 1412 if (!fUserClipping.IsSet()) { 1413 fUserClipping.SetTo(new (nothrow) BRegion); 1414 if (!fUserClipping.IsSet()) 1415 return; 1416 } 1417 1418 fDrawState->GetCombinedClippingRegion(fUserClipping.Get()); 1419 } else { 1420 fUserClipping.SetTo(NULL); 1421 } 1422 1423 fScreenAndUserClipping.SetTo(NULL); 1424 fScreenClippingValid = false; 1425 } 1426 1427 1428 BRegion& 1429 View::ScreenAndUserClipping(const BRegion* windowContentClipping, bool force) const 1430 { 1431 // no user clipping - return screen clipping directly 1432 if (!fUserClipping.IsSet()) 1433 return _ScreenClipping(windowContentClipping, force); 1434 1435 // combined screen and user clipping already valid 1436 if (fScreenAndUserClipping.IsSet()) 1437 return *fScreenAndUserClipping.Get(); 1438 1439 // build a new combined user and screen clipping 1440 fScreenAndUserClipping.SetTo(new (nothrow) BRegion(*fUserClipping.Get())); 1441 if (!fScreenAndUserClipping.IsSet()) 1442 return fScreenClipping; 1443 1444 LocalToScreenTransform().Apply(fScreenAndUserClipping.Get()); 1445 fScreenAndUserClipping->IntersectWith( 1446 &_ScreenClipping(windowContentClipping, force)); 1447 return *fScreenAndUserClipping.Get(); 1448 } 1449 1450 1451 void 1452 View::InvalidateScreenClipping() 1453 { 1454 // TODO: appearantly, we are calling ScreenClipping() on 1455 // views who's parents don't have a valid screen clipping yet, 1456 // this messes up the logic that for any given view with 1457 // fScreenClippingValid == false, all children have 1458 // fScreenClippingValid == false too. If this could be made the 1459 // case, we could save some performance here with the commented 1460 // out check, since InvalidateScreenClipping() might be called 1461 // frequently. 1462 // TODO: investigate, if InvalidateScreenClipping() could be 1463 // called in "deep" and "non-deep" mode, ie. see if there are 1464 // any cases where the children would still have valid screen 1465 // clipping, even though the parent's screen clipping becomes 1466 // invalid. 1467 // if (!fScreenClippingValid) 1468 // return; 1469 1470 fScreenAndUserClipping.SetTo(NULL); 1471 fScreenClippingValid = false; 1472 // invalidate the childrens screen clipping as well 1473 for (View* child = FirstChild(); child; child = child->NextSibling()) { 1474 child->InvalidateScreenClipping(); 1475 } 1476 } 1477 1478 1479 BRegion& 1480 View::_ScreenClipping(const BRegion* windowContentClipping, bool force) const 1481 { 1482 if (!fScreenClippingValid || force) { 1483 fScreenClipping = fLocalClipping; 1484 LocalToScreenTransform().Apply(&fScreenClipping); 1485 1486 // see if parts of our bounds are hidden underneath 1487 // the parent, the local clipping does not account for this 1488 IntRect clippedBounds = Bounds(); 1489 ConvertToVisibleInTopView(&clippedBounds); 1490 if (clippedBounds.Width() < fScreenClipping.Frame().Width() 1491 || clippedBounds.Height() < fScreenClipping.Frame().Height()) { 1492 BRegion temp; 1493 temp.Set((clipping_rect)clippedBounds); 1494 fScreenClipping.IntersectWith(&temp); 1495 } 1496 1497 fScreenClipping.IntersectWith(windowContentClipping); 1498 fScreenClippingValid = true; 1499 } 1500 1501 return fScreenClipping; 1502 } 1503 1504 1505 void 1506 View::_MoveScreenClipping(int32 x, int32 y, bool deep) 1507 { 1508 if (fScreenClippingValid) { 1509 fScreenClipping.OffsetBy(x, y); 1510 fScreenAndUserClipping.SetTo(NULL); 1511 } 1512 1513 if (deep) { 1514 // move the childrens screen clipping as well 1515 for (View* child = FirstChild(); child; child = child->NextSibling()) { 1516 child->_MoveScreenClipping(x, y, deep); 1517 } 1518 } 1519 } 1520 1521