1/* 2 * Copyright 2001-2020 Haiku, Inc. 3 * Distributed under the terms of the MIT License. 4 * 5 * Authors: 6 * Stephan A��mus, superstippi@gmx.de 7 * DarkWyrm, bpmagic@columbus.rr.com 8 * Ryan Leavengood, leavengood@gmail.com 9 * Philippe Saint-Pierre, stpere@gmail.com 10 * John Scipione, jscipione@gmail.com 11 * Ingo Weinhold, ingo_weinhold@gmx.de 12 * Clemens Zeidler, haiku@clemens-zeidler.de 13 * Joseph Groover, looncraz@looncraz.net 14 * Jacob Secunda, secundaja@gmail.com 15 */ 16 17 18/*! Decorator made up of tabs */ 19 20 21#include "TabDecorator.h" 22 23#include <algorithm> 24#include <cmath> 25#include <new> 26#include <stdio.h> 27 28#include <Autolock.h> 29#include <Debug.h> 30#include <GradientLinear.h> 31#include <Rect.h> 32#include <View.h> 33 34#include <WindowPrivate.h> 35 36#include "BitmapDrawingEngine.h" 37#include "DesktopSettings.h" 38#include "DrawingEngine.h" 39#include "DrawState.h" 40#include "FontManager.h" 41#include "PatternHandler.h" 42 43 44//#define DEBUG_DECORATOR 45#ifdef DEBUG_DECORATOR 46# define STRACE(x) printf x 47#else 48# define STRACE(x) ; 49#endif 50 51 52static bool 53int_equal(float x, float y) 54{ 55 return abs(x - y) <= 1; 56} 57 58 59static const float kBorderResizeLength = 22.0; 60static const float kResizeKnobSize = 18.0; 61 62 63// #pragma mark - 64 65 66// TODO: get rid of DesktopSettings here, and introduce private accessor 67// methods to the Decorator base class 68TabDecorator::TabDecorator(DesktopSettings& settings, BRect frame, 69 Desktop* desktop) 70 : 71 Decorator(settings, frame, desktop), 72 fOldMovingTab(0, 0, -1, -1) 73{ 74 STRACE(("TabDecorator:\n")); 75 STRACE(("\tFrame (%.1f,%.1f,%.1f,%.1f)\n", 76 frame.left, frame.top, frame.right, frame.bottom)); 77 78 // TODO: If the decorator was created with a frame too small, it should 79 // resize itself! 80} 81 82 83TabDecorator::~TabDecorator() 84{ 85 STRACE(("TabDecorator: ~TabDecorator()\n")); 86} 87 88 89// #pragma mark - Public methods 90 91 92/*! \brief Updates the decorator in the rectangular area \a updateRect. 93 94 Updates all areas which intersect the frame and tab. 95 96 \param updateRect The rectangular area to update. 97*/ 98void 99TabDecorator::Draw(BRect updateRect) 100{ 101 STRACE(("TabDecorator::Draw(BRect " 102 "updateRect(l:%.1f, t:%.1f, r:%.1f, b:%.1f))\n", 103 updateRect.left, updateRect.top, updateRect.right, updateRect.bottom)); 104 105 fDrawingEngine->SetDrawState(&fDrawState); 106 107 _DrawFrame(updateRect & fBorderRect); 108 109 if (IsOutlineResizing()) 110 _DrawOutlineFrame(updateRect & fOutlineBorderRect); 111 112 _DrawTabs(updateRect & fTitleBarRect); 113} 114 115 116//! Forces a complete decorator update 117void 118TabDecorator::Draw() 119{ 120 STRACE(("TabDecorator: Draw()")); 121 122 fDrawingEngine->SetDrawState(&fDrawState); 123 124 _DrawFrame(fBorderRect); 125 126 if (IsOutlineResizing()) 127 _DrawOutlineFrame(fOutlineBorderRect); 128 129 _DrawTabs(fTitleBarRect); 130} 131 132 133Decorator::Region 134TabDecorator::RegionAt(BPoint where, int32& tab) const 135{ 136 // Let the base class version identify hits of the buttons and the tab. 137 Region region = Decorator::RegionAt(where, tab); 138 if (region != REGION_NONE) 139 return region; 140 141 // check the resize corner 142 if (fTopTab->look == B_DOCUMENT_WINDOW_LOOK && fResizeRect.Contains(where)) 143 return REGION_RIGHT_BOTTOM_CORNER; 144 145 // hit-test the borders 146 if (fLeftBorder.Contains(where)) 147 return REGION_LEFT_BORDER; 148 if (fTopBorder.Contains(where)) 149 return REGION_TOP_BORDER; 150 151 // Part of the bottom and right borders may be a resize-region, so we have 152 // to check explicitly, if it has been it. 153 if (fRightBorder.Contains(where)) 154 region = REGION_RIGHT_BORDER; 155 else if (fBottomBorder.Contains(where)) 156 region = REGION_BOTTOM_BORDER; 157 else 158 return REGION_NONE; 159 160 // check resize area 161 if ((fTopTab->flags & B_NOT_RESIZABLE) == 0 162 && (fTopTab->look == B_TITLED_WINDOW_LOOK 163 || fTopTab->look == B_FLOATING_WINDOW_LOOK 164 || fTopTab->look == B_MODAL_WINDOW_LOOK 165 || fTopTab->look == kLeftTitledWindowLook)) { 166 BRect resizeRect(BPoint(fBottomBorder.right - fBorderResizeLength, 167 fBottomBorder.bottom - fBorderResizeLength), 168 fBottomBorder.RightBottom()); 169 if (resizeRect.Contains(where)) 170 return REGION_RIGHT_BOTTOM_CORNER; 171 } 172 173 return region; 174} 175 176 177bool 178TabDecorator::SetRegionHighlight(Region region, uint8 highlight, 179 BRegion* dirty, int32 tabIndex) 180{ 181 Decorator::Tab* tab 182 = static_cast<Decorator::Tab*>(_TabAt(tabIndex)); 183 if (tab != NULL) { 184 tab->isHighlighted = highlight != 0; 185 // Invalidate the bitmap caches for the close/zoom button, when the 186 // highlight changes. 187 switch (region) { 188 case REGION_CLOSE_BUTTON: 189 if (highlight != RegionHighlight(region)) 190 memset(&tab->closeBitmaps, 0, sizeof(tab->closeBitmaps)); 191 break; 192 case REGION_ZOOM_BUTTON: 193 if (highlight != RegionHighlight(region)) 194 memset(&tab->zoomBitmaps, 0, sizeof(tab->zoomBitmaps)); 195 break; 196 default: 197 break; 198 } 199 } 200 201 return Decorator::SetRegionHighlight(region, highlight, dirty, tabIndex); 202} 203 204 205void 206TabDecorator::UpdateColors(DesktopSettings& settings) 207{ 208 // Desktop is write locked, so be quick about it. 209 fFocusFrameColor = settings.UIColor(B_WINDOW_BORDER_COLOR); 210 fFocusTabColor = settings.UIColor(B_WINDOW_TAB_COLOR); 211 fFocusTabColorLight = tint_color(fFocusTabColor, 212 (B_LIGHTEN_MAX_TINT + B_LIGHTEN_2_TINT) / 2); 213 fFocusTabColorBevel = tint_color(fFocusTabColor, B_LIGHTEN_2_TINT); 214 fFocusTabColorShadow = tint_color(fFocusTabColor, 215 (B_DARKEN_1_TINT + B_NO_TINT) / 2); 216 fFocusTextColor = settings.UIColor(B_WINDOW_TEXT_COLOR); 217 218 fNonFocusFrameColor = settings.UIColor(B_WINDOW_INACTIVE_BORDER_COLOR); 219 fNonFocusTabColor = settings.UIColor(B_WINDOW_INACTIVE_TAB_COLOR); 220 fNonFocusTabColorLight = tint_color(fNonFocusTabColor, 221 (B_LIGHTEN_MAX_TINT + B_LIGHTEN_2_TINT) / 2); 222 fNonFocusTabColorBevel = tint_color(fNonFocusTabColor, B_LIGHTEN_2_TINT); 223 fNonFocusTabColorShadow = tint_color(fNonFocusTabColor, 224 (B_DARKEN_1_TINT + B_NO_TINT) / 2); 225 fNonFocusTextColor = settings.UIColor(B_WINDOW_INACTIVE_TEXT_COLOR); 226} 227 228 229void 230TabDecorator::_DoLayout() 231{ 232 STRACE(("TabDecorator: Do Layout\n")); 233 // Here we determine the size of every rectangle that we use 234 // internally when we are given the size of the client rectangle. 235 236 bool hasTab = false; 237 238 // TODO: Put this computation somewhere more central! 239 const float scaleFactor = max_c(fDrawState.Font().Size() / 12.0f, 1.0f); 240 241 switch ((int)fTopTab->look) { 242 case B_MODAL_WINDOW_LOOK: 243 fBorderWidth = 5; 244 break; 245 246 case B_TITLED_WINDOW_LOOK: 247 case B_DOCUMENT_WINDOW_LOOK: 248 hasTab = true; 249 fBorderWidth = 5; 250 break; 251 case B_FLOATING_WINDOW_LOOK: 252 case kLeftTitledWindowLook: 253 hasTab = true; 254 fBorderWidth = 3; 255 break; 256 257 case B_BORDERED_WINDOW_LOOK: 258 fBorderWidth = 1; 259 break; 260 261 default: 262 fBorderWidth = 0; 263 } 264 265 fBorderWidth = int32(fBorderWidth * scaleFactor); 266 fResizeKnobSize = kResizeKnobSize * scaleFactor; 267 fBorderResizeLength = kBorderResizeLength * scaleFactor; 268 269 // calculate left/top/right/bottom borders 270 if (fBorderWidth > 0) { 271 // NOTE: no overlapping, the left and right border rects 272 // don't include the corners! 273 fLeftBorder.Set(fFrame.left - fBorderWidth, fFrame.top, 274 fFrame.left - 1, fFrame.bottom); 275 276 fRightBorder.Set(fFrame.right + 1, fFrame.top , 277 fFrame.right + fBorderWidth, fFrame.bottom); 278 279 fTopBorder.Set(fFrame.left - fBorderWidth, fFrame.top - fBorderWidth, 280 fFrame.right + fBorderWidth, fFrame.top - 1); 281 282 fBottomBorder.Set(fFrame.left - fBorderWidth, fFrame.bottom + 1, 283 fFrame.right + fBorderWidth, fFrame.bottom + fBorderWidth); 284 } else { 285 // no border 286 fLeftBorder.Set(0.0, 0.0, -1.0, -1.0); 287 fRightBorder.Set(0.0, 0.0, -1.0, -1.0); 288 fTopBorder.Set(0.0, 0.0, -1.0, -1.0); 289 fBottomBorder.Set(0.0, 0.0, -1.0, -1.0); 290 } 291 292 fBorderRect = BRect(fTopBorder.LeftTop(), fBottomBorder.RightBottom()); 293 294 // calculate resize rect 295 if (fBorderWidth > 1) { 296 fResizeRect.Set(fBottomBorder.right - fResizeKnobSize, 297 fBottomBorder.bottom - fResizeKnobSize, fBottomBorder.right, 298 fBottomBorder.bottom); 299 } else { 300 // no border or one pixel border (menus and such) 301 fResizeRect.Set(0, 0, -1, -1); 302 } 303 304 if (hasTab) { 305 _DoTabLayout(); 306 return; 307 } else { 308 // no tab 309 for (int32 i = 0; i < fTabList.CountItems(); i++) { 310 Decorator::Tab* tab = fTabList.ItemAt(i); 311 tab->tabRect.Set(0.0, 0.0, -1.0, -1.0); 312 } 313 fTabsRegion.MakeEmpty(); 314 fTitleBarRect.Set(0.0, 0.0, -1.0, -1.0); 315 } 316} 317 318 319void 320TabDecorator::_DoOutlineLayout() 321{ 322 fOutlineBorderWidth = 1; 323 324 // calculate left/top/right/bottom outline borders 325 // NOTE: no overlapping, the left and right border rects 326 // don't include the corners! 327 fLeftOutlineBorder.Set(fFrame.left - fOutlineBorderWidth, fFrame.top, 328 fFrame.left - 1, fFrame.bottom); 329 330 fRightOutlineBorder.Set(fFrame.right + 1, fFrame.top , 331 fFrame.right + fOutlineBorderWidth, fFrame.bottom); 332 333 fTopOutlineBorder.Set(fFrame.left - fOutlineBorderWidth, 334 fFrame.top - fOutlineBorderWidth, 335 fFrame.right + fOutlineBorderWidth, fFrame.top - 1); 336 337 fBottomOutlineBorder.Set(fFrame.left - fOutlineBorderWidth, 338 fFrame.bottom + 1, 339 fFrame.right + fOutlineBorderWidth, 340 fFrame.bottom + fOutlineBorderWidth); 341 342 fOutlineBorderRect = BRect(fTopOutlineBorder.LeftTop(), 343 fBottomOutlineBorder.RightBottom()); 344} 345 346 347void 348TabDecorator::_DoTabLayout() 349{ 350 float tabOffset = 0; 351 if (fTabList.CountItems() == 1) { 352 float tabSize; 353 tabOffset = _SingleTabOffsetAndSize(tabSize); 354 } 355 356 float sumTabWidth = 0; 357 // calculate our tab rect 358 for (int32 i = 0; i < fTabList.CountItems(); i++) { 359 Decorator::Tab* tab = _TabAt(i); 360 361 BRect& tabRect = tab->tabRect; 362 // distance from one item of the tab bar to another. 363 // In this case the text and close/zoom rects 364 tab->textOffset = _DefaultTextOffset(); 365 366 font_height fontHeight; 367 fDrawState.Font().GetHeight(fontHeight); 368 369 if (tab->look != kLeftTitledWindowLook) { 370 const float spacing = fBorderWidth * 1.4f; 371 tabRect.Set(fFrame.left - fBorderWidth, 372 fFrame.top - fBorderWidth 373 - ceilf(fontHeight.ascent + fontHeight.descent + spacing), 374 ((fFrame.right - fFrame.left) < (spacing * 5) ? 375 fFrame.left + (spacing * 5) : fFrame.right) + fBorderWidth, 376 fFrame.top - fBorderWidth); 377 } else { 378 tabRect.Set(fFrame.left - fBorderWidth 379 - ceilf(fontHeight.ascent + fontHeight.descent + fBorderWidth), 380 fFrame.top - fBorderWidth, fFrame.left - fBorderWidth, 381 fFrame.bottom + fBorderWidth); 382 } 383 384 // format tab rect for a floating window - make the rect smaller 385 if (tab->look == B_FLOATING_WINDOW_LOOK) { 386 tabRect.InsetBy(0, 2); 387 tabRect.OffsetBy(0, 2); 388 } 389 390 float offset; 391 float size; 392 float inset; 393 _GetButtonSizeAndOffset(tabRect, &offset, &size, &inset); 394 395 // tab->minTabSize contains just the room for the buttons 396 tab->minTabSize = inset * 2 + tab->textOffset; 397 if ((tab->flags & B_NOT_CLOSABLE) == 0) 398 tab->minTabSize += offset + size; 399 if ((tab->flags & B_NOT_ZOOMABLE) == 0) 400 tab->minTabSize += offset + size; 401 402 // tab->maxTabSize contains tab->minTabSize + the width required for the 403 // title 404 tab->maxTabSize = fDrawingEngine 405 ? ceilf(fDrawingEngine->StringWidth(Title(tab), strlen(Title(tab)), 406 fDrawState.Font())) : 0.0; 407 if (tab->maxTabSize > 0.0) 408 tab->maxTabSize += tab->textOffset; 409 tab->maxTabSize += tab->minTabSize; 410 411 float tabSize = (tab->look != kLeftTitledWindowLook 412 ? fFrame.Width() : fFrame.Height()) + fBorderWidth * 2; 413 if (tabSize < tab->minTabSize) 414 tabSize = tab->minTabSize; 415 if (tabSize > tab->maxTabSize) 416 tabSize = tab->maxTabSize; 417 418 // layout buttons and truncate text 419 if (tab->look != kLeftTitledWindowLook) 420 tabRect.right = tabRect.left + tabSize; 421 else 422 tabRect.bottom = tabRect.top + tabSize; 423 424 // make sure fTabOffset is within limits and apply it to 425 // the tabRect 426 tab->tabOffset = (uint32)tabOffset; 427 if (tab->tabLocation != 0.0 && fTabList.CountItems() == 1 428 && tab->tabOffset > (fRightBorder.right - fLeftBorder.left 429 - tabRect.Width())) { 430 tab->tabOffset = uint32(fRightBorder.right - fLeftBorder.left 431 - tabRect.Width()); 432 } 433 tabRect.OffsetBy(tab->tabOffset, 0); 434 tabOffset += tabRect.Width(); 435 436 sumTabWidth += tabRect.Width(); 437 } 438 439 float windowWidth = fFrame.Width() + 2 * fBorderWidth; 440 if (CountTabs() > 1 && sumTabWidth > windowWidth) 441 _DistributeTabSize(sumTabWidth - windowWidth); 442 443 // finally, layout the buttons and text within the tab rect 444 for (int32 i = 0; i < fTabList.CountItems(); i++) { 445 Decorator::Tab* tab = fTabList.ItemAt(i); 446 447 if (i == 0) 448 fTitleBarRect = tab->tabRect; 449 else 450 fTitleBarRect = fTitleBarRect | tab->tabRect; 451 452 _LayoutTabItems(tab, tab->tabRect); 453 } 454 455 fTabsRegion = fTitleBarRect; 456} 457 458 459void 460TabDecorator::_DistributeTabSize(float delta) 461{ 462 int32 tabCount = fTabList.CountItems(); 463 ASSERT(tabCount > 1); 464 465 float maxTabSize = 0; 466 float secMaxTabSize = 0; 467 int32 nTabsWithMaxSize = 0; 468 for (int32 i = 0; i < tabCount; i++) { 469 Decorator::Tab* tab = fTabList.ItemAt(i); 470 if (tab == NULL) 471 continue; 472 473 float tabWidth = tab->tabRect.Width(); 474 if (int_equal(maxTabSize, tabWidth)) { 475 nTabsWithMaxSize++; 476 continue; 477 } 478 if (maxTabSize < tabWidth) { 479 secMaxTabSize = maxTabSize; 480 maxTabSize = tabWidth; 481 nTabsWithMaxSize = 1; 482 } else if (secMaxTabSize <= tabWidth) 483 secMaxTabSize = tabWidth; 484 } 485 486 float minus = ceilf(std::min(maxTabSize - secMaxTabSize, delta)); 487 if (minus < 1.0) 488 return; 489 delta -= minus; 490 minus /= nTabsWithMaxSize; 491 492 Decorator::Tab* previousTab = NULL; 493 for (int32 i = 0; i < tabCount; i++) { 494 Decorator::Tab* tab = fTabList.ItemAt(i); 495 if (tab == NULL) 496 continue; 497 498 if (int_equal(maxTabSize, tab->tabRect.Width())) 499 tab->tabRect.right -= minus; 500 501 if (previousTab != NULL) { 502 float offsetX = previousTab->tabRect.right - tab->tabRect.left; 503 tab->tabRect.OffsetBy(offsetX, 0); 504 } 505 506 previousTab = tab; 507 } 508 509 if (delta > 0) { 510 _DistributeTabSize(delta); 511 return; 512 } 513 514 // done 515 if (previousTab != NULL) 516 previousTab->tabRect.right = floorf(fFrame.right + fBorderWidth); 517 518 for (int32 i = 0; i < tabCount; i++) { 519 Decorator::Tab* tab = fTabList.ItemAt(i); 520 if (tab == NULL) 521 continue; 522 523 tab->tabOffset = uint32(tab->tabRect.left - fLeftBorder.left); 524 } 525} 526 527 528void 529TabDecorator::_DrawOutlineFrame(BRect rect) 530{ 531 drawing_mode oldMode; 532 533 fDrawingEngine->SetDrawingMode(B_OP_ALPHA, oldMode); 534 fDrawingEngine->SetPattern(B_MIXED_COLORS); 535 fDrawingEngine->StrokeRect(rect); 536 537 fDrawingEngine->SetDrawingMode(oldMode); 538} 539 540 541void 542TabDecorator::_SetTitle(Decorator::Tab* tab, const char* string, 543 BRegion* updateRegion) 544{ 545 // TODO: we could be much smarter about the update region 546 547 BRect rect = TabRect((int32) 0) | TabRect(CountTabs() - 1); 548 // Get a rect of all the tabs 549 550 _DoLayout(); 551 _DoOutlineLayout(); 552 553 if (updateRegion == NULL) 554 return; 555 556 rect = rect | TabRect(CountTabs() - 1); 557 // Update the rect to guarantee it updates all the tabs 558 559 rect.bottom++; 560 // the border will look differently when the title is adjacent 561 562 updateRegion->Include(rect); 563} 564 565 566void 567TabDecorator::_MoveBy(BPoint offset) 568{ 569 STRACE(("TabDecorator: Move By (%.1f, %.1f)\n", offset.x, offset.y)); 570 571 // Move all internal rectangles the appropriate amount 572 for (int32 i = 0; i < fTabList.CountItems(); i++) { 573 Decorator::Tab* tab = fTabList.ItemAt(i); 574 tab->zoomRect.OffsetBy(offset); 575 tab->closeRect.OffsetBy(offset); 576 tab->tabRect.OffsetBy(offset); 577 } 578 579 fFrame.OffsetBy(offset); 580 fTitleBarRect.OffsetBy(offset); 581 fTabsRegion.OffsetBy(offset); 582 fResizeRect.OffsetBy(offset); 583 fBorderRect.OffsetBy(offset); 584 585 fLeftBorder.OffsetBy(offset); 586 fRightBorder.OffsetBy(offset); 587 fTopBorder.OffsetBy(offset); 588 fBottomBorder.OffsetBy(offset); 589} 590 591 592void 593TabDecorator::_ResizeBy(BPoint offset, BRegion* dirty) 594{ 595 STRACE(("TabDecorator: Resize By (%.1f, %.1f)\n", offset.x, offset.y)); 596 597 // Move all internal rectangles the appropriate amount 598 fFrame.right += offset.x; 599 fFrame.bottom += offset.y; 600 601 // Handle invalidation of resize rect 602 if (dirty != NULL && !(fTopTab->flags & B_NOT_RESIZABLE)) { 603 BRect realResizeRect; 604 switch ((int)fTopTab->look) { 605 case B_DOCUMENT_WINDOW_LOOK: 606 realResizeRect = fResizeRect; 607 // Resize rect at old location 608 dirty->Include(realResizeRect); 609 realResizeRect.OffsetBy(offset); 610 // Resize rect at new location 611 dirty->Include(realResizeRect); 612 break; 613 614 case B_TITLED_WINDOW_LOOK: 615 case B_FLOATING_WINDOW_LOOK: 616 case B_MODAL_WINDOW_LOOK: 617 case kLeftTitledWindowLook: 618 // The bottom border resize line 619 realResizeRect.Set(fRightBorder.right - fBorderResizeLength, 620 fBottomBorder.top, 621 fRightBorder.right - fBorderResizeLength, 622 fBottomBorder.bottom - 1); 623 // Old location 624 dirty->Include(realResizeRect); 625 realResizeRect.OffsetBy(offset); 626 // New location 627 dirty->Include(realResizeRect); 628 629 // The right border resize line 630 realResizeRect.Set(fRightBorder.left, 631 fBottomBorder.bottom - fBorderResizeLength, 632 fRightBorder.right - 1, 633 fBottomBorder.bottom - fBorderResizeLength); 634 // Old location 635 dirty->Include(realResizeRect); 636 realResizeRect.OffsetBy(offset); 637 // New location 638 dirty->Include(realResizeRect); 639 break; 640 641 default: 642 break; 643 } 644 } 645 646 fResizeRect.OffsetBy(offset); 647 648 fBorderRect.right += offset.x; 649 fBorderRect.bottom += offset.y; 650 651 fLeftBorder.bottom += offset.y; 652 fTopBorder.right += offset.x; 653 654 fRightBorder.OffsetBy(offset.x, 0.0); 655 fRightBorder.bottom += offset.y; 656 657 fBottomBorder.OffsetBy(0.0, offset.y); 658 fBottomBorder.right += offset.x; 659 660 if (dirty) { 661 if (offset.x > 0.0) { 662 BRect t(fRightBorder.left - offset.x, fTopBorder.top, 663 fRightBorder.right, fTopBorder.bottom); 664 dirty->Include(t); 665 t.Set(fRightBorder.left - offset.x, fBottomBorder.top, 666 fRightBorder.right, fBottomBorder.bottom); 667 dirty->Include(t); 668 dirty->Include(fRightBorder); 669 } else if (offset.x < 0.0) { 670 dirty->Include(BRect(fRightBorder.left, fTopBorder.top, 671 fRightBorder.right, fBottomBorder.bottom)); 672 } 673 if (offset.y > 0.0) { 674 BRect t(fLeftBorder.left, fLeftBorder.bottom - offset.y, 675 fLeftBorder.right, fLeftBorder.bottom); 676 dirty->Include(t); 677 t.Set(fRightBorder.left, fRightBorder.bottom - offset.y, 678 fRightBorder.right, fRightBorder.bottom); 679 dirty->Include(t); 680 dirty->Include(fBottomBorder); 681 } else if (offset.y < 0.0) { 682 dirty->Include(fBottomBorder); 683 } 684 } 685 686 // resize tab and layout tab items 687 if (fTitleBarRect.IsValid()) { 688 if (fTabList.CountItems() > 1) { 689 _DoTabLayout(); 690 if (dirty != NULL) 691 dirty->Include(fTitleBarRect); 692 return; 693 } 694 695 Decorator::Tab* tab = _TabAt(0); 696 BRect& tabRect = tab->tabRect; 697 BRect oldTabRect(tabRect); 698 699 float tabSize; 700 float tabOffset = _SingleTabOffsetAndSize(tabSize); 701 702 float delta = tabOffset - tab->tabOffset; 703 tab->tabOffset = (uint32)tabOffset; 704 if (fTopTab->look != kLeftTitledWindowLook) 705 tabRect.OffsetBy(delta, 0.0); 706 else 707 tabRect.OffsetBy(0.0, delta); 708 709 if (tabSize < tab->minTabSize) 710 tabSize = tab->minTabSize; 711 if (tabSize > tab->maxTabSize) 712 tabSize = tab->maxTabSize; 713 714 if (fTopTab->look != kLeftTitledWindowLook 715 && tabSize != tabRect.Width()) { 716 tabRect.right = tabRect.left + tabSize; 717 } else if (fTopTab->look == kLeftTitledWindowLook 718 && tabSize != tabRect.Height()) { 719 tabRect.bottom = tabRect.top + tabSize; 720 } 721 722 if (oldTabRect != tabRect) { 723 _LayoutTabItems(tab, tabRect); 724 725 if (dirty) { 726 // NOTE: the tab rect becoming smaller only would 727 // handled be the Desktop anyways, so it is sufficient 728 // to include it into the dirty region in it's 729 // final state 730 BRect redraw(tabRect); 731 if (delta != 0.0) { 732 redraw = redraw | oldTabRect; 733 if (fTopTab->look != kLeftTitledWindowLook) 734 redraw.bottom++; 735 else 736 redraw.right++; 737 } 738 dirty->Include(redraw); 739 } 740 } 741 fTitleBarRect = tabRect; 742 fTabsRegion = fTitleBarRect; 743 } 744} 745 746 747void 748TabDecorator::_SetFocus(Decorator::Tab* tab) 749{ 750 Decorator::Tab* decoratorTab = static_cast<Decorator::Tab*>(tab); 751 752 decoratorTab->buttonFocus = IsFocus(tab) 753 || ((decoratorTab->look == B_FLOATING_WINDOW_LOOK 754 || decoratorTab->look == kLeftTitledWindowLook) 755 && (decoratorTab->flags & B_AVOID_FOCUS) != 0); 756 if (CountTabs() > 1) 757 _LayoutTabItems(decoratorTab, decoratorTab->tabRect); 758} 759 760 761bool 762TabDecorator::_SetTabLocation(Decorator::Tab* _tab, float location, 763 bool isShifting, BRegion* updateRegion) 764{ 765 STRACE(("TabDecorator: Set Tab Location(%.1f)\n", location)); 766 767 if (CountTabs() > 1) { 768 if (isShifting == false) { 769 _DoTabLayout(); 770 if (updateRegion != NULL) 771 updateRegion->Include(fTitleBarRect); 772 773 fOldMovingTab = BRect(0, 0, -1, -1); 774 return true; 775 } else { 776 if (fOldMovingTab.IsValid() == false) 777 fOldMovingTab = _tab->tabRect; 778 } 779 } 780 781 Decorator::Tab* tab = static_cast<Decorator::Tab*>(_tab); 782 BRect& tabRect = tab->tabRect; 783 if (tabRect.IsValid() == false) 784 return false; 785 786 if (location < 0) 787 location = 0; 788 789 float maxLocation 790 = fRightBorder.right - fLeftBorder.left - tabRect.Width(); 791 if (CountTabs() > 1) 792 maxLocation = fTitleBarRect.right - fLeftBorder.left - tabRect.Width(); 793 794 if (location > maxLocation) 795 location = maxLocation; 796 797 float delta = floor(location - tab->tabOffset); 798 if (delta == 0.0) 799 return false; 800 801 // redraw old rect (1 pixel on the border must also be updated) 802 BRect rect(tabRect); 803 rect.bottom++; 804 if (updateRegion != NULL) 805 updateRegion->Include(rect); 806 807 tabRect.OffsetBy(delta, 0); 808 tab->tabOffset = (int32)location; 809 _LayoutTabItems(_tab, tabRect); 810 tab->tabLocation = maxLocation > 0.0 ? tab->tabOffset / maxLocation : 0.0; 811 812 if (fTabList.CountItems() == 1) 813 fTitleBarRect = tabRect; 814 815 _CalculateTabsRegion(); 816 817 // redraw new rect as well 818 rect = tabRect; 819 rect.bottom++; 820 if (updateRegion != NULL) 821 updateRegion->Include(rect); 822 823 return true; 824} 825 826 827bool 828TabDecorator::_SetSettings(const BMessage& settings, BRegion* updateRegion) 829{ 830 float tabLocation; 831 bool modified = false; 832 for (int32 i = 0; i < fTabList.CountItems(); i++) { 833 if (settings.FindFloat("tab location", i, &tabLocation) != B_OK) 834 return false; 835 modified |= SetTabLocation(i, tabLocation, updateRegion); 836 } 837 return modified; 838} 839 840 841bool 842TabDecorator::_AddTab(DesktopSettings& settings, int32 index, 843 BRegion* updateRegion) 844{ 845 _UpdateFont(settings); 846 847 _DoLayout(); 848 _DoOutlineLayout(); 849 850 if (updateRegion != NULL) 851 updateRegion->Include(fTitleBarRect); 852 return true; 853} 854 855 856bool 857TabDecorator::_RemoveTab(int32 index, BRegion* updateRegion) 858{ 859 BRect oldRect = TabRect(index) | TabRect(CountTabs() - 1); 860 // Get a rect of all the tabs to the right - they will all be moved 861 862 _DoLayout(); 863 _DoOutlineLayout(); 864 865 if (updateRegion != NULL) { 866 updateRegion->Include(oldRect); 867 updateRegion->Include(fTitleBarRect); 868 } 869 return true; 870} 871 872 873bool 874TabDecorator::_MoveTab(int32 from, int32 to, bool isMoving, 875 BRegion* updateRegion) 876{ 877 Decorator::Tab* toTab = _TabAt(to); 878 if (toTab == NULL) 879 return false; 880 881 if (from < to) { 882 fOldMovingTab.OffsetBy(toTab->tabRect.Width(), 0); 883 toTab->tabRect.OffsetBy(-fOldMovingTab.Width(), 0); 884 } else { 885 fOldMovingTab.OffsetBy(-toTab->tabRect.Width(), 0); 886 toTab->tabRect.OffsetBy(fOldMovingTab.Width(), 0); 887 } 888 889 toTab->tabOffset = uint32(toTab->tabRect.left - fLeftBorder.left); 890 _LayoutTabItems(toTab, toTab->tabRect); 891 892 _CalculateTabsRegion(); 893 894 if (updateRegion != NULL) 895 updateRegion->Include(fTitleBarRect); 896 return true; 897} 898 899 900void 901TabDecorator::_GetFootprint(BRegion *region) 902{ 903 STRACE(("TabDecorator: GetFootprint\n")); 904 905 // This function calculates the decorator's footprint in coordinates 906 // relative to the view. This is most often used to set a Window 907 // object's visible region. 908 909 if (region == NULL) 910 return; 911 912 if (fTopTab->look == B_NO_BORDER_WINDOW_LOOK) 913 return; 914 915 region->Include(fTopBorder); 916 region->Include(fLeftBorder); 917 region->Include(fRightBorder); 918 region->Include(fBottomBorder); 919 920 if (fTopTab->look == B_BORDERED_WINDOW_LOOK) 921 return; 922 923 region->Include(&fTabsRegion); 924 925 if (fTopTab->look == B_DOCUMENT_WINDOW_LOOK) { 926 // include the rectangular resize knob on the bottom right 927 float knobSize = fResizeKnobSize - fBorderWidth; 928 region->Include(BRect(fFrame.right - knobSize, fFrame.bottom - knobSize, 929 fFrame.right, fFrame.bottom)); 930 } 931} 932 933 934void 935TabDecorator::_DrawButtons(Decorator::Tab* tab, const BRect& invalid) 936{ 937 STRACE(("TabDecorator: _DrawButtons\n")); 938 939 // Draw the buttons if we're supposed to 940 if (!(tab->flags & B_NOT_CLOSABLE) && invalid.Intersects(tab->closeRect)) 941 _DrawClose(tab, false, tab->closeRect); 942 if (!(tab->flags & B_NOT_ZOOMABLE) && invalid.Intersects(tab->zoomRect)) 943 _DrawZoom(tab, false, tab->zoomRect); 944} 945 946 947void 948TabDecorator::_UpdateFont(DesktopSettings& settings) 949{ 950 ServerFont font; 951 if (fTopTab->look == B_FLOATING_WINDOW_LOOK 952 || fTopTab->look == kLeftTitledWindowLook) { 953 settings.GetDefaultPlainFont(font); 954 if (fTopTab->look == kLeftTitledWindowLook) 955 font.SetRotation(90.0f); 956 } else 957 settings.GetDefaultBoldFont(font); 958 959 font.SetFlags(B_FORCE_ANTIALIASING); 960 font.SetSpacing(B_STRING_SPACING); 961 fDrawState.SetFont(font); 962} 963 964 965void 966TabDecorator::_GetButtonSizeAndOffset(const BRect& tabRect, float* _offset, 967 float* _size, float* _inset) const 968{ 969 float tabSize = fTopTab->look == kLeftTitledWindowLook ? 970 tabRect.Width() : tabRect.Height(); 971 972 bool smallTab = fTopTab->look == B_FLOATING_WINDOW_LOOK 973 || fTopTab->look == kLeftTitledWindowLook; 974 975 *_offset = smallTab ? floorf(fDrawState.Font().Size() / 2.6) 976 : floorf(fDrawState.Font().Size() / 2.3); 977 *_inset = smallTab ? floorf(fDrawState.Font().Size() / 5.0) 978 : floorf(fDrawState.Font().Size() / 6.0); 979 980 // "+ 2" so that the rects are centered within the solid area 981 // (without the 2 pixels for the top border) 982 *_size = tabSize - 2 * *_offset + *_inset; 983} 984 985 986void 987TabDecorator::_LayoutTabItems(Decorator::Tab* _tab, const BRect& tabRect) 988{ 989 Decorator::Tab* tab = static_cast<Decorator::Tab*>(_tab); 990 991 float offset; 992 float size; 993 float inset; 994 _GetButtonSizeAndOffset(tabRect, &offset, &size, &inset); 995 996 // default textOffset 997 tab->textOffset = _DefaultTextOffset(); 998 999 BRect& closeRect = tab->closeRect; 1000 BRect& zoomRect = tab->zoomRect; 1001 1002 // calulate close rect based on the tab rectangle 1003 if (tab->look != kLeftTitledWindowLook) { 1004 closeRect.Set(tabRect.left + offset, tabRect.top + offset, 1005 tabRect.left + offset + size, tabRect.top + offset + size); 1006 1007 zoomRect.Set(tabRect.right - offset - size, tabRect.top + offset, 1008 tabRect.right - offset, tabRect.top + offset + size); 1009 1010 // hidden buttons have no width 1011 if ((tab->flags & B_NOT_CLOSABLE) != 0) 1012 closeRect.right = closeRect.left - offset; 1013 if ((tab->flags & B_NOT_ZOOMABLE) != 0) 1014 zoomRect.left = zoomRect.right + offset; 1015 } else { 1016 closeRect.Set(tabRect.left + offset, tabRect.top + offset, 1017 tabRect.left + offset + size, tabRect.top + offset + size); 1018 1019 zoomRect.Set(tabRect.left + offset, tabRect.bottom - offset - size, 1020 tabRect.left + size + offset, tabRect.bottom - offset); 1021 1022 // hidden buttons have no height 1023 if ((tab->flags & B_NOT_CLOSABLE) != 0) 1024 closeRect.bottom = closeRect.top - offset; 1025 if ((tab->flags & B_NOT_ZOOMABLE) != 0) 1026 zoomRect.top = zoomRect.bottom + offset; 1027 } 1028 1029 // calculate room for title 1030 // TODO: the +2 is there because the title often appeared 1031 // truncated for no apparent reason - OTOH the title does 1032 // also not appear perfectly in the middle 1033 if (tab->look != kLeftTitledWindowLook) 1034 size = (zoomRect.left - closeRect.right) - tab->textOffset * 2 + inset; 1035 else 1036 size = (zoomRect.top - closeRect.bottom) - tab->textOffset * 2 + inset; 1037 1038 bool stackMode = fTabList.CountItems() > 1; 1039 if (stackMode && IsFocus(tab) == false) { 1040 zoomRect.Set(0, 0, 0, 0); 1041 size = (tab->tabRect.right - closeRect.right) - tab->textOffset * 2 1042 + inset; 1043 } 1044 uint8 truncateMode = B_TRUNCATE_MIDDLE; 1045 if (stackMode) { 1046 if (tab->tabRect.Width() < 100) 1047 truncateMode = B_TRUNCATE_END; 1048 float titleWidth = fDrawState.Font().StringWidth(Title(tab), 1049 BString(Title(tab)).Length()); 1050 if (size < titleWidth) { 1051 float oldTextOffset = tab->textOffset; 1052 tab->textOffset -= (titleWidth - size) / 2; 1053 const float kMinTextOffset = 5.; 1054 if (tab->textOffset < kMinTextOffset) 1055 tab->textOffset = kMinTextOffset; 1056 size += oldTextOffset * 2; 1057 size -= tab->textOffset * 2; 1058 } 1059 } 1060 tab->truncatedTitle = Title(tab); 1061 fDrawState.Font().TruncateString(&tab->truncatedTitle, truncateMode, size); 1062 tab->truncatedTitleLength = tab->truncatedTitle.Length(); 1063} 1064 1065 1066float 1067TabDecorator::_DefaultTextOffset() const 1068{ 1069 if (fTopTab->look == B_FLOATING_WINDOW_LOOK 1070 || fTopTab->look == kLeftTitledWindowLook) 1071 return int32(fBorderWidth * 3.4f); 1072 return int32(fBorderWidth * 3.6f); 1073} 1074 1075 1076float 1077TabDecorator::_SingleTabOffsetAndSize(float& tabSize) 1078{ 1079 float maxLocation; 1080 if (fTopTab->look != kLeftTitledWindowLook) { 1081 tabSize = fRightBorder.right - fLeftBorder.left; 1082 } else { 1083 tabSize = fBottomBorder.bottom - fTopBorder.top; 1084 } 1085 Decorator::Tab* tab = _TabAt(0); 1086 maxLocation = tabSize - tab->maxTabSize; 1087 if (maxLocation < 0) 1088 maxLocation = 0; 1089 1090 return floorf(tab->tabLocation * maxLocation); 1091} 1092 1093 1094void 1095TabDecorator::_CalculateTabsRegion() 1096{ 1097 fTabsRegion.MakeEmpty(); 1098 for (int32 i = 0; i < fTabList.CountItems(); i++) 1099 fTabsRegion.Include(fTabList.ItemAt(i)->tabRect); 1100} 1101