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