/* * Copyright 2001-2020 Haiku, Inc. * Distributed under the terms of the MIT License. * * Authors: * Stephan Aßmus, superstippi@gmx.de * DarkWyrm, bpmagic@columbus.rr.com * Ryan Leavengood, leavengood@gmail.com * Philippe Saint-Pierre, stpere@gmail.com * John Scipione, jscipione@gmail.com * Ingo Weinhold, ingo_weinhold@gmx.de * Clemens Zeidler, haiku@clemens-zeidler.de * Joseph Groover, looncraz@looncraz.net * Jacob Secunda, secundaja@gmail.com */ /*! Decorator made up of tabs */ #include "TabDecorator.h" #include #include #include #include #include #include #include #include #include #include #include "BitmapDrawingEngine.h" #include "DesktopSettings.h" #include "DrawingEngine.h" #include "DrawState.h" #include "FontManager.h" #include "PatternHandler.h" //#define DEBUG_DECORATOR #ifdef DEBUG_DECORATOR # define STRACE(x) printf x #else # define STRACE(x) ; #endif static bool int_equal(float x, float y) { return abs(x - y) <= 1; } static const float kBorderResizeLength = 22.0; static const float kResizeKnobSize = 18.0; // #pragma mark - // TODO: get rid of DesktopSettings here, and introduce private accessor // methods to the Decorator base class TabDecorator::TabDecorator(DesktopSettings& settings, BRect frame, Desktop* desktop) : Decorator(settings, frame, desktop), fOldMovingTab(0, 0, -1, -1) { STRACE(("TabDecorator:\n")); STRACE(("\tFrame (%.1f,%.1f,%.1f,%.1f)\n", frame.left, frame.top, frame.right, frame.bottom)); // TODO: If the decorator was created with a frame too small, it should // resize itself! } TabDecorator::~TabDecorator() { STRACE(("TabDecorator: ~TabDecorator()\n")); } // #pragma mark - Public methods /*! \brief Updates the decorator in the rectangular area \a updateRect. Updates all areas which intersect the frame and tab. \param updateRect The rectangular area to update. */ void TabDecorator::Draw(BRect updateRect) { STRACE(("TabDecorator::Draw(BRect " "updateRect(l:%.1f, t:%.1f, r:%.1f, b:%.1f))\n", updateRect.left, updateRect.top, updateRect.right, updateRect.bottom)); fDrawingEngine->SetDrawState(&fDrawState); _DrawFrame(updateRect & fBorderRect); if (IsOutlineResizing()) _DrawOutlineFrame(updateRect & fOutlineBorderRect); _DrawTabs(updateRect & fTitleBarRect); } //! Forces a complete decorator update void TabDecorator::Draw() { STRACE(("TabDecorator: Draw()")); fDrawingEngine->SetDrawState(&fDrawState); _DrawFrame(fBorderRect); if (IsOutlineResizing()) _DrawOutlineFrame(fOutlineBorderRect); _DrawTabs(fTitleBarRect); } Decorator::Region TabDecorator::RegionAt(BPoint where, int32& tab) const { // Let the base class version identify hits of the buttons and the tab. Region region = Decorator::RegionAt(where, tab); if (region != REGION_NONE) return region; // check the resize corner if (fTopTab->look == B_DOCUMENT_WINDOW_LOOK && fResizeRect.Contains(where)) return REGION_RIGHT_BOTTOM_CORNER; // hit-test the borders if (fLeftBorder.Contains(where)) return REGION_LEFT_BORDER; if (fTopBorder.Contains(where)) return REGION_TOP_BORDER; // Part of the bottom and right borders may be a resize-region, so we have // to check explicitly, if it has been it. if (fRightBorder.Contains(where)) region = REGION_RIGHT_BORDER; else if (fBottomBorder.Contains(where)) region = REGION_BOTTOM_BORDER; else return REGION_NONE; // check resize area if ((fTopTab->flags & B_NOT_RESIZABLE) == 0 && (fTopTab->look == B_TITLED_WINDOW_LOOK || fTopTab->look == B_FLOATING_WINDOW_LOOK || fTopTab->look == B_MODAL_WINDOW_LOOK || fTopTab->look == kLeftTitledWindowLook)) { BRect resizeRect(BPoint(fBottomBorder.right - fBorderResizeLength, fBottomBorder.bottom - fBorderResizeLength), fBottomBorder.RightBottom()); if (resizeRect.Contains(where)) return REGION_RIGHT_BOTTOM_CORNER; } return region; } bool TabDecorator::SetRegionHighlight(Region region, uint8 highlight, BRegion* dirty, int32 tabIndex) { Decorator::Tab* tab = static_cast(_TabAt(tabIndex)); if (tab != NULL) { tab->isHighlighted = highlight != 0; // Invalidate the bitmap caches for the close/zoom button, when the // highlight changes. switch (region) { case REGION_CLOSE_BUTTON: if (highlight != RegionHighlight(region)) memset(&tab->closeBitmaps, 0, sizeof(tab->closeBitmaps)); break; case REGION_ZOOM_BUTTON: if (highlight != RegionHighlight(region)) memset(&tab->zoomBitmaps, 0, sizeof(tab->zoomBitmaps)); break; default: break; } } return Decorator::SetRegionHighlight(region, highlight, dirty, tabIndex); } void TabDecorator::UpdateColors(DesktopSettings& settings) { // Desktop is write locked, so be quick about it. fFocusFrameColor = settings.UIColor(B_WINDOW_BORDER_COLOR); fFocusTabColor = settings.UIColor(B_WINDOW_TAB_COLOR); fFocusTabColorLight = tint_color(fFocusTabColor, (B_LIGHTEN_MAX_TINT + B_LIGHTEN_2_TINT) / 2); fFocusTabColorBevel = tint_color(fFocusTabColor, B_LIGHTEN_2_TINT); fFocusTabColorShadow = tint_color(fFocusTabColor, (B_DARKEN_1_TINT + B_NO_TINT) / 2); fFocusTextColor = settings.UIColor(B_WINDOW_TEXT_COLOR); fNonFocusFrameColor = settings.UIColor(B_WINDOW_INACTIVE_BORDER_COLOR); fNonFocusTabColor = settings.UIColor(B_WINDOW_INACTIVE_TAB_COLOR); fNonFocusTabColorLight = tint_color(fNonFocusTabColor, (B_LIGHTEN_MAX_TINT + B_LIGHTEN_2_TINT) / 2); fNonFocusTabColorBevel = tint_color(fNonFocusTabColor, B_LIGHTEN_2_TINT); fNonFocusTabColorShadow = tint_color(fNonFocusTabColor, (B_DARKEN_1_TINT + B_NO_TINT) / 2); fNonFocusTextColor = settings.UIColor(B_WINDOW_INACTIVE_TEXT_COLOR); } void TabDecorator::_DoLayout() { STRACE(("TabDecorator: Do Layout\n")); // Here we determine the size of every rectangle that we use // internally when we are given the size of the client rectangle. bool hasTab = false; // TODO: Put this computation somewhere more central! const float scaleFactor = max_c(fDrawState.Font().Size() / 12.0f, 1.0f); switch ((int)fTopTab->look) { case B_MODAL_WINDOW_LOOK: fBorderWidth = 5; break; case B_TITLED_WINDOW_LOOK: case B_DOCUMENT_WINDOW_LOOK: hasTab = true; fBorderWidth = 5; break; case B_FLOATING_WINDOW_LOOK: case kLeftTitledWindowLook: hasTab = true; fBorderWidth = 3; break; case B_BORDERED_WINDOW_LOOK: fBorderWidth = 1; break; default: fBorderWidth = 0; } fBorderWidth = int32(fBorderWidth * scaleFactor); fResizeKnobSize = kResizeKnobSize * scaleFactor; fBorderResizeLength = kBorderResizeLength * scaleFactor; // calculate left/top/right/bottom borders if (fBorderWidth > 0) { // NOTE: no overlapping, the left and right border rects // don't include the corners! fLeftBorder.Set(fFrame.left - fBorderWidth, fFrame.top, fFrame.left - 1, fFrame.bottom); fRightBorder.Set(fFrame.right + 1, fFrame.top , fFrame.right + fBorderWidth, fFrame.bottom); fTopBorder.Set(fFrame.left - fBorderWidth, fFrame.top - fBorderWidth, fFrame.right + fBorderWidth, fFrame.top - 1); fBottomBorder.Set(fFrame.left - fBorderWidth, fFrame.bottom + 1, fFrame.right + fBorderWidth, fFrame.bottom + fBorderWidth); } else { // no border fLeftBorder.Set(0.0, 0.0, -1.0, -1.0); fRightBorder.Set(0.0, 0.0, -1.0, -1.0); fTopBorder.Set(0.0, 0.0, -1.0, -1.0); fBottomBorder.Set(0.0, 0.0, -1.0, -1.0); } fBorderRect = BRect(fTopBorder.LeftTop(), fBottomBorder.RightBottom()); // calculate resize rect if (fBorderWidth > 1) { fResizeRect.Set(fBottomBorder.right - fResizeKnobSize, fBottomBorder.bottom - fResizeKnobSize, fBottomBorder.right, fBottomBorder.bottom); } else { // no border or one pixel border (menus and such) fResizeRect.Set(0, 0, -1, -1); } if (hasTab) { _DoTabLayout(); return; } else { // no tab for (int32 i = 0; i < fTabList.CountItems(); i++) { Decorator::Tab* tab = fTabList.ItemAt(i); tab->tabRect.Set(0.0, 0.0, -1.0, -1.0); } fTabsRegion.MakeEmpty(); fTitleBarRect.Set(0.0, 0.0, -1.0, -1.0); } } void TabDecorator::_DoOutlineLayout() { fOutlineBorderWidth = 1; // calculate left/top/right/bottom outline borders // NOTE: no overlapping, the left and right border rects // don't include the corners! fLeftOutlineBorder.Set(fFrame.left - fOutlineBorderWidth, fFrame.top, fFrame.left - 1, fFrame.bottom); fRightOutlineBorder.Set(fFrame.right + 1, fFrame.top , fFrame.right + fOutlineBorderWidth, fFrame.bottom); fTopOutlineBorder.Set(fFrame.left - fOutlineBorderWidth, fFrame.top - fOutlineBorderWidth, fFrame.right + fOutlineBorderWidth, fFrame.top - 1); fBottomOutlineBorder.Set(fFrame.left - fOutlineBorderWidth, fFrame.bottom + 1, fFrame.right + fOutlineBorderWidth, fFrame.bottom + fOutlineBorderWidth); fOutlineBorderRect = BRect(fTopOutlineBorder.LeftTop(), fBottomOutlineBorder.RightBottom()); } void TabDecorator::_DoTabLayout() { float tabOffset = 0; if (fTabList.CountItems() == 1) { float tabSize; tabOffset = _SingleTabOffsetAndSize(tabSize); } float sumTabWidth = 0; // calculate our tab rect for (int32 i = 0; i < fTabList.CountItems(); i++) { Decorator::Tab* tab = _TabAt(i); BRect& tabRect = tab->tabRect; // distance from one item of the tab bar to another. // In this case the text and close/zoom rects tab->textOffset = _DefaultTextOffset(); font_height fontHeight; fDrawState.Font().GetHeight(fontHeight); if (tab->look != kLeftTitledWindowLook) { const float spacing = fBorderWidth * 1.4f; tabRect.Set(fFrame.left - fBorderWidth, fFrame.top - fBorderWidth - ceilf(fontHeight.ascent + fontHeight.descent + spacing), ((fFrame.right - fFrame.left) < (spacing * 5) ? fFrame.left + (spacing * 5) : fFrame.right) + fBorderWidth, fFrame.top - fBorderWidth); } else { tabRect.Set(fFrame.left - fBorderWidth - ceilf(fontHeight.ascent + fontHeight.descent + fBorderWidth), fFrame.top - fBorderWidth, fFrame.left - fBorderWidth, fFrame.bottom + fBorderWidth); } // format tab rect for a floating window - make the rect smaller if (tab->look == B_FLOATING_WINDOW_LOOK) { tabRect.InsetBy(0, 2); tabRect.OffsetBy(0, 2); } float offset; float size; float inset; _GetButtonSizeAndOffset(tabRect, &offset, &size, &inset); // tab->minTabSize contains just the room for the buttons tab->minTabSize = inset * 2 + tab->textOffset; if ((tab->flags & B_NOT_CLOSABLE) == 0) tab->minTabSize += offset + size; if ((tab->flags & B_NOT_ZOOMABLE) == 0) tab->minTabSize += offset + size; // tab->maxTabSize contains tab->minTabSize + the width required for the // title tab->maxTabSize = fDrawingEngine ? ceilf(fDrawingEngine->StringWidth(Title(tab), strlen(Title(tab)), fDrawState.Font())) : 0.0; if (tab->maxTabSize > 0.0) tab->maxTabSize += tab->textOffset; tab->maxTabSize += tab->minTabSize; float tabSize = (tab->look != kLeftTitledWindowLook ? fFrame.Width() : fFrame.Height()) + fBorderWidth * 2; if (tabSize < tab->minTabSize) tabSize = tab->minTabSize; if (tabSize > tab->maxTabSize) tabSize = tab->maxTabSize; // layout buttons and truncate text if (tab->look != kLeftTitledWindowLook) tabRect.right = tabRect.left + tabSize; else tabRect.bottom = tabRect.top + tabSize; // make sure fTabOffset is within limits and apply it to // the tabRect tab->tabOffset = (uint32)tabOffset; if (tab->tabLocation != 0.0 && fTabList.CountItems() == 1 && tab->tabOffset > (fRightBorder.right - fLeftBorder.left - tabRect.Width())) { tab->tabOffset = uint32(fRightBorder.right - fLeftBorder.left - tabRect.Width()); } tabRect.OffsetBy(tab->tabOffset, 0); tabOffset += tabRect.Width(); sumTabWidth += tabRect.Width(); } float windowWidth = fFrame.Width() + 2 * fBorderWidth; if (CountTabs() > 1 && sumTabWidth > windowWidth) _DistributeTabSize(sumTabWidth - windowWidth); // finally, layout the buttons and text within the tab rect for (int32 i = 0; i < fTabList.CountItems(); i++) { Decorator::Tab* tab = fTabList.ItemAt(i); if (i == 0) fTitleBarRect = tab->tabRect; else fTitleBarRect = fTitleBarRect | tab->tabRect; _LayoutTabItems(tab, tab->tabRect); } fTabsRegion = fTitleBarRect; } void TabDecorator::_DistributeTabSize(float delta) { int32 tabCount = fTabList.CountItems(); ASSERT(tabCount > 1); float maxTabSize = 0; float secMaxTabSize = 0; int32 nTabsWithMaxSize = 0; for (int32 i = 0; i < tabCount; i++) { Decorator::Tab* tab = fTabList.ItemAt(i); if (tab == NULL) continue; float tabWidth = tab->tabRect.Width(); if (int_equal(maxTabSize, tabWidth)) { nTabsWithMaxSize++; continue; } if (maxTabSize < tabWidth) { secMaxTabSize = maxTabSize; maxTabSize = tabWidth; nTabsWithMaxSize = 1; } else if (secMaxTabSize <= tabWidth) secMaxTabSize = tabWidth; } float minus = ceilf(std::min(maxTabSize - secMaxTabSize, delta)); if (minus < 1.0) return; delta -= minus; minus /= nTabsWithMaxSize; Decorator::Tab* previousTab = NULL; for (int32 i = 0; i < tabCount; i++) { Decorator::Tab* tab = fTabList.ItemAt(i); if (tab == NULL) continue; if (int_equal(maxTabSize, tab->tabRect.Width())) tab->tabRect.right -= minus; if (previousTab != NULL) { float offsetX = previousTab->tabRect.right - tab->tabRect.left; tab->tabRect.OffsetBy(offsetX, 0); } previousTab = tab; } if (delta > 0) { _DistributeTabSize(delta); return; } // done if (previousTab != NULL) previousTab->tabRect.right = floorf(fFrame.right + fBorderWidth); for (int32 i = 0; i < tabCount; i++) { Decorator::Tab* tab = fTabList.ItemAt(i); if (tab == NULL) continue; tab->tabOffset = uint32(tab->tabRect.left - fLeftBorder.left); } } void TabDecorator::_DrawOutlineFrame(BRect rect) { drawing_mode oldMode; fDrawingEngine->SetDrawingMode(B_OP_ALPHA, oldMode); fDrawingEngine->SetPattern(B_MIXED_COLORS); fDrawingEngine->StrokeRect(rect); fDrawingEngine->SetDrawingMode(oldMode); } void TabDecorator::_SetTitle(Decorator::Tab* tab, const char* string, BRegion* updateRegion) { // TODO: we could be much smarter about the update region BRect rect = TabRect((int32) 0) | TabRect(CountTabs() - 1); // Get a rect of all the tabs _DoLayout(); _DoOutlineLayout(); if (updateRegion == NULL) return; rect = rect | TabRect(CountTabs() - 1); // Update the rect to guarantee it updates all the tabs rect.bottom++; // the border will look differently when the title is adjacent updateRegion->Include(rect); } void TabDecorator::_MoveBy(BPoint offset) { STRACE(("TabDecorator: Move By (%.1f, %.1f)\n", offset.x, offset.y)); // Move all internal rectangles the appropriate amount for (int32 i = 0; i < fTabList.CountItems(); i++) { Decorator::Tab* tab = fTabList.ItemAt(i); tab->zoomRect.OffsetBy(offset); tab->closeRect.OffsetBy(offset); tab->tabRect.OffsetBy(offset); } fFrame.OffsetBy(offset); fTitleBarRect.OffsetBy(offset); fTabsRegion.OffsetBy(offset); fResizeRect.OffsetBy(offset); fBorderRect.OffsetBy(offset); fLeftBorder.OffsetBy(offset); fRightBorder.OffsetBy(offset); fTopBorder.OffsetBy(offset); fBottomBorder.OffsetBy(offset); } void TabDecorator::_ResizeBy(BPoint offset, BRegion* dirty) { STRACE(("TabDecorator: Resize By (%.1f, %.1f)\n", offset.x, offset.y)); // Move all internal rectangles the appropriate amount fFrame.right += offset.x; fFrame.bottom += offset.y; // Handle invalidation of resize rect if (dirty != NULL && !(fTopTab->flags & B_NOT_RESIZABLE)) { BRect realResizeRect; switch ((int)fTopTab->look) { case B_DOCUMENT_WINDOW_LOOK: realResizeRect = fResizeRect; // Resize rect at old location dirty->Include(realResizeRect); realResizeRect.OffsetBy(offset); // Resize rect at new location dirty->Include(realResizeRect); break; case B_TITLED_WINDOW_LOOK: case B_FLOATING_WINDOW_LOOK: case B_MODAL_WINDOW_LOOK: case kLeftTitledWindowLook: // The bottom border resize line realResizeRect.Set(fRightBorder.right - fBorderResizeLength, fBottomBorder.top, fRightBorder.right - fBorderResizeLength, fBottomBorder.bottom - 1); // Old location dirty->Include(realResizeRect); realResizeRect.OffsetBy(offset); // New location dirty->Include(realResizeRect); // The right border resize line realResizeRect.Set(fRightBorder.left, fBottomBorder.bottom - fBorderResizeLength, fRightBorder.right - 1, fBottomBorder.bottom - fBorderResizeLength); // Old location dirty->Include(realResizeRect); realResizeRect.OffsetBy(offset); // New location dirty->Include(realResizeRect); break; default: break; } } fResizeRect.OffsetBy(offset); fBorderRect.right += offset.x; fBorderRect.bottom += offset.y; fLeftBorder.bottom += offset.y; fTopBorder.right += offset.x; fRightBorder.OffsetBy(offset.x, 0.0); fRightBorder.bottom += offset.y; fBottomBorder.OffsetBy(0.0, offset.y); fBottomBorder.right += offset.x; if (dirty) { if (offset.x > 0.0) { BRect t(fRightBorder.left - offset.x, fTopBorder.top, fRightBorder.right, fTopBorder.bottom); dirty->Include(t); t.Set(fRightBorder.left - offset.x, fBottomBorder.top, fRightBorder.right, fBottomBorder.bottom); dirty->Include(t); dirty->Include(fRightBorder); } else if (offset.x < 0.0) { dirty->Include(BRect(fRightBorder.left, fTopBorder.top, fRightBorder.right, fBottomBorder.bottom)); } if (offset.y > 0.0) { BRect t(fLeftBorder.left, fLeftBorder.bottom - offset.y, fLeftBorder.right, fLeftBorder.bottom); dirty->Include(t); t.Set(fRightBorder.left, fRightBorder.bottom - offset.y, fRightBorder.right, fRightBorder.bottom); dirty->Include(t); dirty->Include(fBottomBorder); } else if (offset.y < 0.0) { dirty->Include(fBottomBorder); } } // resize tab and layout tab items if (fTitleBarRect.IsValid()) { if (fTabList.CountItems() > 1) { _DoTabLayout(); if (dirty != NULL) dirty->Include(fTitleBarRect); return; } Decorator::Tab* tab = _TabAt(0); BRect& tabRect = tab->tabRect; BRect oldTabRect(tabRect); float tabSize; float tabOffset = _SingleTabOffsetAndSize(tabSize); float delta = tabOffset - tab->tabOffset; tab->tabOffset = (uint32)tabOffset; if (fTopTab->look != kLeftTitledWindowLook) tabRect.OffsetBy(delta, 0.0); else tabRect.OffsetBy(0.0, delta); if (tabSize < tab->minTabSize) tabSize = tab->minTabSize; if (tabSize > tab->maxTabSize) tabSize = tab->maxTabSize; if (fTopTab->look != kLeftTitledWindowLook && tabSize != tabRect.Width()) { tabRect.right = tabRect.left + tabSize; } else if (fTopTab->look == kLeftTitledWindowLook && tabSize != tabRect.Height()) { tabRect.bottom = tabRect.top + tabSize; } if (oldTabRect != tabRect) { _LayoutTabItems(tab, tabRect); if (dirty) { // NOTE: the tab rect becoming smaller only would // handled be the Desktop anyways, so it is sufficient // to include it into the dirty region in it's // final state BRect redraw(tabRect); if (delta != 0.0) { redraw = redraw | oldTabRect; if (fTopTab->look != kLeftTitledWindowLook) redraw.bottom++; else redraw.right++; } dirty->Include(redraw); } } fTitleBarRect = tabRect; fTabsRegion = fTitleBarRect; } } void TabDecorator::_SetFocus(Decorator::Tab* tab) { Decorator::Tab* decoratorTab = static_cast(tab); decoratorTab->buttonFocus = IsFocus(tab) || ((decoratorTab->look == B_FLOATING_WINDOW_LOOK || decoratorTab->look == kLeftTitledWindowLook) && (decoratorTab->flags & B_AVOID_FOCUS) != 0); if (CountTabs() > 1) _LayoutTabItems(decoratorTab, decoratorTab->tabRect); } bool TabDecorator::_SetTabLocation(Decorator::Tab* _tab, float location, bool isShifting, BRegion* updateRegion) { STRACE(("TabDecorator: Set Tab Location(%.1f)\n", location)); if (CountTabs() > 1) { if (isShifting == false) { _DoTabLayout(); if (updateRegion != NULL) updateRegion->Include(fTitleBarRect); fOldMovingTab = BRect(0, 0, -1, -1); return true; } else { if (fOldMovingTab.IsValid() == false) fOldMovingTab = _tab->tabRect; } } Decorator::Tab* tab = static_cast(_tab); BRect& tabRect = tab->tabRect; if (tabRect.IsValid() == false) return false; if (location < 0) location = 0; float maxLocation = fRightBorder.right - fLeftBorder.left - tabRect.Width(); if (CountTabs() > 1) maxLocation = fTitleBarRect.right - fLeftBorder.left - tabRect.Width(); if (location > maxLocation) location = maxLocation; float delta = floor(location - tab->tabOffset); if (delta == 0.0) return false; // redraw old rect (1 pixel on the border must also be updated) BRect rect(tabRect); rect.bottom++; if (updateRegion != NULL) updateRegion->Include(rect); tabRect.OffsetBy(delta, 0); tab->tabOffset = (int32)location; _LayoutTabItems(_tab, tabRect); tab->tabLocation = maxLocation > 0.0 ? tab->tabOffset / maxLocation : 0.0; if (fTabList.CountItems() == 1) fTitleBarRect = tabRect; _CalculateTabsRegion(); // redraw new rect as well rect = tabRect; rect.bottom++; if (updateRegion != NULL) updateRegion->Include(rect); return true; } bool TabDecorator::_SetSettings(const BMessage& settings, BRegion* updateRegion) { float tabLocation; bool modified = false; for (int32 i = 0; i < fTabList.CountItems(); i++) { if (settings.FindFloat("tab location", i, &tabLocation) != B_OK) return false; modified |= SetTabLocation(i, tabLocation, updateRegion); } return modified; } bool TabDecorator::_AddTab(DesktopSettings& settings, int32 index, BRegion* updateRegion) { _UpdateFont(settings); _DoLayout(); _DoOutlineLayout(); if (updateRegion != NULL) updateRegion->Include(fTitleBarRect); return true; } bool TabDecorator::_RemoveTab(int32 index, BRegion* updateRegion) { BRect oldRect = TabRect(index) | TabRect(CountTabs() - 1); // Get a rect of all the tabs to the right - they will all be moved _DoLayout(); _DoOutlineLayout(); if (updateRegion != NULL) { updateRegion->Include(oldRect); updateRegion->Include(fTitleBarRect); } return true; } bool TabDecorator::_MoveTab(int32 from, int32 to, bool isMoving, BRegion* updateRegion) { Decorator::Tab* toTab = _TabAt(to); if (toTab == NULL) return false; if (from < to) { fOldMovingTab.OffsetBy(toTab->tabRect.Width(), 0); toTab->tabRect.OffsetBy(-fOldMovingTab.Width(), 0); } else { fOldMovingTab.OffsetBy(-toTab->tabRect.Width(), 0); toTab->tabRect.OffsetBy(fOldMovingTab.Width(), 0); } toTab->tabOffset = uint32(toTab->tabRect.left - fLeftBorder.left); _LayoutTabItems(toTab, toTab->tabRect); _CalculateTabsRegion(); if (updateRegion != NULL) updateRegion->Include(fTitleBarRect); return true; } void TabDecorator::_GetFootprint(BRegion *region) { STRACE(("TabDecorator: GetFootprint\n")); // This function calculates the decorator's footprint in coordinates // relative to the view. This is most often used to set a Window // object's visible region. if (region == NULL) return; if (fTopTab->look == B_NO_BORDER_WINDOW_LOOK) return; region->Include(fTopBorder); region->Include(fLeftBorder); region->Include(fRightBorder); region->Include(fBottomBorder); if (fTopTab->look == B_BORDERED_WINDOW_LOOK) return; region->Include(&fTabsRegion); if (fTopTab->look == B_DOCUMENT_WINDOW_LOOK) { // include the rectangular resize knob on the bottom right float knobSize = fResizeKnobSize - fBorderWidth; region->Include(BRect(fFrame.right - knobSize, fFrame.bottom - knobSize, fFrame.right, fFrame.bottom)); } } void TabDecorator::_DrawButtons(Decorator::Tab* tab, const BRect& invalid) { STRACE(("TabDecorator: _DrawButtons\n")); // Draw the buttons if we're supposed to if (!(tab->flags & B_NOT_CLOSABLE) && invalid.Intersects(tab->closeRect)) _DrawClose(tab, false, tab->closeRect); if (!(tab->flags & B_NOT_ZOOMABLE) && invalid.Intersects(tab->zoomRect)) _DrawZoom(tab, false, tab->zoomRect); } void TabDecorator::_UpdateFont(DesktopSettings& settings) { ServerFont font; if (fTopTab->look == B_FLOATING_WINDOW_LOOK || fTopTab->look == kLeftTitledWindowLook) { settings.GetDefaultPlainFont(font); if (fTopTab->look == kLeftTitledWindowLook) font.SetRotation(90.0f); } else settings.GetDefaultBoldFont(font); font.SetFlags(B_FORCE_ANTIALIASING); font.SetSpacing(B_STRING_SPACING); fDrawState.SetFont(font); } void TabDecorator::_GetButtonSizeAndOffset(const BRect& tabRect, float* _offset, float* _size, float* _inset) const { float tabSize = fTopTab->look == kLeftTitledWindowLook ? tabRect.Width() : tabRect.Height(); bool smallTab = fTopTab->look == B_FLOATING_WINDOW_LOOK || fTopTab->look == kLeftTitledWindowLook; *_offset = smallTab ? floorf(fDrawState.Font().Size() / 2.6) : floorf(fDrawState.Font().Size() / 2.3); *_inset = smallTab ? floorf(fDrawState.Font().Size() / 5.0) : floorf(fDrawState.Font().Size() / 6.0); // "+ 2" so that the rects are centered within the solid area // (without the 2 pixels for the top border) *_size = tabSize - 2 * *_offset + *_inset; } void TabDecorator::_LayoutTabItems(Decorator::Tab* _tab, const BRect& tabRect) { Decorator::Tab* tab = static_cast(_tab); float offset; float size; float inset; _GetButtonSizeAndOffset(tabRect, &offset, &size, &inset); // default textOffset tab->textOffset = _DefaultTextOffset(); BRect& closeRect = tab->closeRect; BRect& zoomRect = tab->zoomRect; // calulate close rect based on the tab rectangle if (tab->look != kLeftTitledWindowLook) { closeRect.Set(tabRect.left + offset, tabRect.top + offset, tabRect.left + offset + size, tabRect.top + offset + size); zoomRect.Set(tabRect.right - offset - size, tabRect.top + offset, tabRect.right - offset, tabRect.top + offset + size); // hidden buttons have no width if ((tab->flags & B_NOT_CLOSABLE) != 0) closeRect.right = closeRect.left - offset; if ((tab->flags & B_NOT_ZOOMABLE) != 0) zoomRect.left = zoomRect.right + offset; } else { closeRect.Set(tabRect.left + offset, tabRect.top + offset, tabRect.left + offset + size, tabRect.top + offset + size); zoomRect.Set(tabRect.left + offset, tabRect.bottom - offset - size, tabRect.left + size + offset, tabRect.bottom - offset); // hidden buttons have no height if ((tab->flags & B_NOT_CLOSABLE) != 0) closeRect.bottom = closeRect.top - offset; if ((tab->flags & B_NOT_ZOOMABLE) != 0) zoomRect.top = zoomRect.bottom + offset; } // calculate room for title // TODO: the +2 is there because the title often appeared // truncated for no apparent reason - OTOH the title does // also not appear perfectly in the middle if (tab->look != kLeftTitledWindowLook) size = (zoomRect.left - closeRect.right) - tab->textOffset * 2 + inset; else size = (zoomRect.top - closeRect.bottom) - tab->textOffset * 2 + inset; bool stackMode = fTabList.CountItems() > 1; if (stackMode && IsFocus(tab) == false) { zoomRect.Set(0, 0, 0, 0); size = (tab->tabRect.right - closeRect.right) - tab->textOffset * 2 + inset; } uint8 truncateMode = B_TRUNCATE_MIDDLE; if (stackMode) { if (tab->tabRect.Width() < 100) truncateMode = B_TRUNCATE_END; float titleWidth = fDrawState.Font().StringWidth(Title(tab), BString(Title(tab)).Length()); if (size < titleWidth) { float oldTextOffset = tab->textOffset; tab->textOffset -= (titleWidth - size) / 2; const float kMinTextOffset = 5.; if (tab->textOffset < kMinTextOffset) tab->textOffset = kMinTextOffset; size += oldTextOffset * 2; size -= tab->textOffset * 2; } } tab->truncatedTitle = Title(tab); fDrawState.Font().TruncateString(&tab->truncatedTitle, truncateMode, size); tab->truncatedTitleLength = tab->truncatedTitle.Length(); } float TabDecorator::_DefaultTextOffset() const { if (fTopTab->look == B_FLOATING_WINDOW_LOOK || fTopTab->look == kLeftTitledWindowLook) return int32(fBorderWidth * 3.4f); return int32(fBorderWidth * 3.6f); } float TabDecorator::_SingleTabOffsetAndSize(float& tabSize) { float maxLocation; if (fTopTab->look != kLeftTitledWindowLook) { tabSize = fRightBorder.right - fLeftBorder.left; } else { tabSize = fBottomBorder.bottom - fTopBorder.top; } Decorator::Tab* tab = _TabAt(0); maxLocation = tabSize - tab->maxTabSize; if (maxLocation < 0) maxLocation = 0; return floorf(tab->tabLocation * maxLocation); } void TabDecorator::_CalculateTabsRegion() { fTabsRegion.MakeEmpty(); for (int32 i = 0; i < fTabList.CountItems(); i++) fTabsRegion.Include(fTabList.ItemAt(i)->tabRect); }