/* * Copyright 2001-2014 Haiku, Inc. All rights reserved. * Distributed under the terms of the MIT License. * * Authors: * Stephan Aßmus, superstippi@gmx.de * DarkWyrm, bpmagic@columbus.rr.com * John Scipione, jscipione@gmail.com * Clemens Zeidler, haiku@clemens-zeidler.de */ /*! Decorator resembling BeOS R5 */ #include "BeDecorator.h" #include #include #include #include #include #include #include #include #include #include #include #include "BitmapDrawingEngine.h" #include "Desktop.h" #include "DesktopSettings.h" #include "DrawingEngine.h" #include "DrawState.h" #include "FontManager.h" #include "PatternHandler.h" #include "RGBColor.h" #include "ServerBitmap.h" //#define DEBUG_DECORATOR #ifdef DEBUG_DECORATOR # define STRACE(x) printf x #else # define STRACE(x) ; #endif static const float kBorderResizeLength = 22.0; static const float kResizeKnobSize = 18.0; static const unsigned char f = 0xff; // way to write 0xff shorter static const unsigned char kInnerShadowBits[] = { f, f, f, f, f, f, f, f, f, 0, f, f, f, f, f, f, 0, f, 0, f, f, f, f, f, f, 0, f, 0, f, 0, f, f, f, f, 0, f, 0, 0, 0, 0, f, f, f, 0, f, 0, 0, 0, 0, 0, f, f, 0, f, 0, 0, 0, 0, 0, 0, f, 0, f, 0, 0, 0, 0, 0, 0, 0, f, f, 0, 0, 0, 0, 0, 0, 0, 0, f, 0, f, 0, 0, 0, 0, 0, 0, 0, 0, f, 0, 0, 0, 0, 0, 0, 0, 0 }; static const unsigned char kOuterShadowBits[] = { f, f, f, f, f, f, f, f, f, f, f, f, f, f, f, f, f, f, f, f, f, f, f, f, f, f, f, f, f, f, f, f, f, f, f, f, f, f, f, f, f, f, f, f, f, f, f, f, f, 0, f, f, f, f, f, f, f, 0, 0, 0, f, f, f, f, f, f, 0, f, 0, 0, f, f, f, f, f, 0, f, 0, 0, 0, f, f, f, f, f, 0, 0, 0, 0, 0, f, f, f, f, 0, 0, 0, 0, 0, 0 }; static const unsigned char kBigInnerShadowBits[] = { f, f, f, f, f, f, f, f, f, f, f, f, f, 0, f, f, f, f, f, 0, 0, f, f, f, f, 0, f, 0, f, f, f, 0, f, 0, 0, f, f, 0, f, 0, 0, 0, f, 0, 0, 0, 0, 0, 0 }; static const unsigned char kBigOuterShadowBits[] = { f, f, f, f, f, f, f, f, f, f, f, f, f, 0, f, f, f, f, f, f, 0, f, f, f, f, f, f, 0, f, f, f, f, f, f, 0, f, f, f, f, f, f, 0, f, 0, 0, 0, 0, 0, 0 }; static const unsigned char kSmallInnerShadowBits[] = { f, f, f, 0, 0, f, f, 0, f, 0, f, 0, f, 0, 0, 0, f, 0, 0, 0, 0, 0, 0, 0, 0 }; static const unsigned char kSmallOuterShadowBits[] = { f, f, f, f, f, f, f, f, f, f, f, f, f, f, f, f, f, f, f, 0, f, f, 0, 0, 0 }; static const unsigned char kGlintBits[] = { 0, f, 0, f, 0, f, 0, f, f }; // #pragma mark - BeDecorAddOn BeDecorAddOn::BeDecorAddOn(image_id id, const char* name) : DecorAddOn(id, name) { } Decorator* BeDecorAddOn::_AllocateDecorator(DesktopSettings& settings, BRect rect, Desktop* desktop) { return new (std::nothrow)BeDecorator(settings, rect, desktop); } // #pragma mark - BeDecorator // TODO: get rid of DesktopSettings here, and introduce private accessor // methods to the Decorator base class BeDecorator::BeDecorator(DesktopSettings& settings, BRect rect, Desktop* desktop) : SATDecorator(settings, rect, desktop), fCStatus(B_NO_INIT) { STRACE(("BeDecorator:\n")); STRACE(("\tFrame (%.1f,%.1f,%.1f,%.1f)\n", rect.left, rect.top, rect.right, rect.bottom)); fCloseBitmap = _CreateTemporaryBitmap(BRect(0, 0, 9, 9)); fBigZoomBitmap = _CreateTemporaryBitmap(BRect(0, 0, 6, 6)); fSmallZoomBitmap = _CreateTemporaryBitmap(BRect(0, 0, 4, 4)); fGlintBitmap = _CreateTemporaryBitmap(BRect(0, 0, 2, 2)); // glint bitmap is used by close and zoom buttons if (fCloseBitmap == NULL || fBigZoomBitmap == NULL || fSmallZoomBitmap == NULL || fGlintBitmap == NULL) { fCStatus = B_NO_MEMORY; } else fCStatus = B_OK; } BeDecorator::~BeDecorator() { STRACE(("BeDecorator: ~BeDecorator()\n")); //delete[] fFrameColors; if (fCloseBitmap != NULL) fCloseBitmap->ReleaseReference(); if (fBigZoomBitmap != NULL) fBigZoomBitmap->ReleaseReference(); if (fSmallZoomBitmap != NULL) fSmallZoomBitmap->ReleaseReference(); if (fGlintBitmap != NULL) fGlintBitmap->ReleaseReference(); } // #pragma mark - Public methods /*! Returns the frame colors for the specified decorator component. The meaning of the color array elements depends on the specified component. For some components some array elements are unused. \param component The component for which to return the frame colors. \param highlight The highlight set for the component. \param colors An array of colors to be initialized by the function. */ void BeDecorator::GetComponentColors(Component component, uint8 highlight, ComponentColors _colors, Decorator::Tab* _tab) { Decorator::Tab* tab = static_cast(_tab); switch (component) { case COMPONENT_TAB: if (highlight == HIGHLIGHT_STACK_AND_TILE) { _colors[COLOR_TAB_FRAME_LIGHT] = tint_color(fFocusFrameColor, B_DARKEN_3_TINT); _colors[COLOR_TAB_FRAME_DARK] = tint_color(fFocusFrameColor, B_DARKEN_4_TINT); _colors[COLOR_TAB] = tint_color(fFocusTabColor, B_DARKEN_1_TINT); _colors[COLOR_TAB_LIGHT] = tint_color(fFocusTabColorLight, B_DARKEN_1_TINT); _colors[COLOR_TAB_BEVEL] = fFocusTabColorBevel; _colors[COLOR_TAB_SHADOW] = fFocusTabColorShadow; _colors[COLOR_TAB_TEXT] = fFocusTextColor; } else if (tab && tab->buttonFocus) { _colors[COLOR_TAB_FRAME_LIGHT] = tint_color(fFocusFrameColor, B_DARKEN_2_TINT); _colors[COLOR_TAB_FRAME_DARK] = tint_color(fFocusFrameColor, B_DARKEN_3_TINT); _colors[COLOR_TAB] = fFocusTabColor; _colors[COLOR_TAB_LIGHT] = fFocusTabColorLight; _colors[COLOR_TAB_BEVEL] = fFocusTabColorBevel; _colors[COLOR_TAB_SHADOW] = fFocusTabColorShadow; _colors[COLOR_TAB_TEXT] = fFocusTextColor; } else { _colors[COLOR_TAB_FRAME_LIGHT] = tint_color(fNonFocusFrameColor, B_DARKEN_2_TINT); _colors[COLOR_TAB_FRAME_DARK] = tint_color(fNonFocusFrameColor, B_DARKEN_3_TINT); _colors[COLOR_TAB] = fNonFocusTabColor; _colors[COLOR_TAB_LIGHT] = fNonFocusTabColorLight; _colors[COLOR_TAB_BEVEL] = fNonFocusTabColorBevel; _colors[COLOR_TAB_SHADOW] = fNonFocusTabColorShadow; _colors[COLOR_TAB_TEXT] = fNonFocusTextColor; } break; case COMPONENT_CLOSE_BUTTON: case COMPONENT_ZOOM_BUTTON: if (highlight == HIGHLIGHT_STACK_AND_TILE) { _colors[COLOR_BUTTON] = tint_color(fFocusTabColor, B_DARKEN_1_TINT); _colors[COLOR_BUTTON_LIGHT] = tint_color(fFocusTabColorLight, B_DARKEN_1_TINT); } else if (tab && tab->buttonFocus) { _colors[COLOR_BUTTON] = fFocusTabColor; _colors[COLOR_BUTTON_LIGHT] = fFocusTabColorLight; } else { _colors[COLOR_BUTTON] = fNonFocusTabColor; _colors[COLOR_BUTTON_LIGHT] = fNonFocusTabColorLight; } break; case COMPONENT_LEFT_BORDER: case COMPONENT_RIGHT_BORDER: case COMPONENT_TOP_BORDER: case COMPONENT_BOTTOM_BORDER: case COMPONENT_RESIZE_CORNER: default: { rgb_color base; if (highlight == HIGHLIGHT_STACK_AND_TILE) base = tint_color(fFocusFrameColor, B_DARKEN_3_TINT); else if (tab && tab->buttonFocus) base = fFocusFrameColor; else base = fNonFocusFrameColor; //_colors[0].SetColor(152, 152, 152); //_colors[1].SetColor(255, 255, 255); //_colors[2].SetColor(216, 216, 216); //_colors[3].SetColor(136, 136, 136); //_colors[4].SetColor(152, 152, 152); //_colors[5].SetColor(96, 96, 96); _colors[0].red = std::max(0, base.red - 72); _colors[0].green = std::max(0, base.green - 72); _colors[0].blue = std::max(0, base.blue - 72); _colors[0].alpha = 255; _colors[1].red = std::min(255, base.red + 64); _colors[1].green = std::min(255, base.green + 64); _colors[1].blue = std::min(255, base.blue + 64); _colors[1].alpha = 255; _colors[2].red = std::max(0, base.red - 8); _colors[2].green = std::max(0, base.green - 8); _colors[2].blue = std::max(0, base.blue - 8); _colors[2].alpha = 255; _colors[3].red = std::max(0, base.red - 88); _colors[3].green = std::max(0, base.green - 88); _colors[3].blue = std::max(0, base.blue - 88); _colors[3].alpha = 255; _colors[4].red = std::max(0, base.red - 72); _colors[4].green = std::max(0, base.green - 72); _colors[4].blue = std::max(0, base.blue - 72); _colors[4].alpha = 255; _colors[5].red = std::max(0, base.red - 128); _colors[5].green = std::max(0, base.green - 128); _colors[5].blue = std::max(0, base.blue - 128); _colors[5].alpha = 255; // for the resize-border highlight dye everything bluish. if (highlight == HIGHLIGHT_RESIZE_BORDER) { for (int32 i = 0; i < 6; i++) { _colors[i].red = std::max((int)_colors[i].red - 80, 0); _colors[i].green = std::max((int)_colors[i].green - 80, 0); _colors[i].blue = 255; } } break; } } } // #pragma mark - Protected methods void BeDecorator::_DrawFrame(BRect invalid) { STRACE(("_DrawFrame(%f,%f,%f,%f)\n", invalid.left, invalid.top, invalid.right, invalid.bottom)); // NOTE: the DrawingEngine needs to be locked for the entire // time for the clipping to stay valid for this decorator if (fTopTab->look == B_NO_BORDER_WINDOW_LOOK) return; if (fBorderWidth <= 0) return; // Draw the border frame BRect r = BRect(fTopBorder.LeftTop(), fBottomBorder.RightBottom()); switch ((int)fTopTab->look) { case B_TITLED_WINDOW_LOOK: case B_DOCUMENT_WINDOW_LOOK: case B_MODAL_WINDOW_LOOK: { // top if (invalid.Intersects(fTopBorder)) { ComponentColors colors; _GetComponentColors(COMPONENT_TOP_BORDER, colors, fTopTab); for (int8 i = 0; i < 5; i++) { fDrawingEngine->StrokeLine(BPoint(r.left + i, r.top + i), BPoint(r.right - i, r.top + i), colors[i]); } if (fTitleBarRect.IsValid()) { // grey along the bottom of the tab // (overwrites "white" from frame) fDrawingEngine->StrokeLine( BPoint(fTitleBarRect.left + 2, fTitleBarRect.bottom + 1), BPoint(fTitleBarRect.right - 2, fTitleBarRect.bottom + 1), colors[2]); } } // left if (invalid.Intersects(fLeftBorder.InsetByCopy(0, -fBorderWidth))) { ComponentColors colors; _GetComponentColors(COMPONENT_LEFT_BORDER, colors, fTopTab); for (int8 i = 0; i < 5; i++) { fDrawingEngine->StrokeLine(BPoint(r.left + i, r.top + i), BPoint(r.left + i, r.bottom - i), colors[i]); } } // bottom if (invalid.Intersects(fBottomBorder)) { ComponentColors colors; _GetComponentColors(COMPONENT_BOTTOM_BORDER, colors, fTopTab); for (int8 i = 0; i < 5; i++) { fDrawingEngine->StrokeLine(BPoint(r.left + i, r.bottom - i), BPoint(r.right - i, r.bottom - i), colors[(4 - i) == 4 ? 5 : (4 - i)]); } } // right if (invalid.Intersects( fRightBorder.InsetByCopy(0, -fBorderWidth))) { ComponentColors colors; _GetComponentColors(COMPONENT_RIGHT_BORDER, colors, fTopTab); for (int8 i = 0; i < 5; i++) { fDrawingEngine->StrokeLine(BPoint(r.right - i, r.top + i), BPoint(r.right - i, r.bottom - i), colors[(4 - i) == 4 ? 5 : (4 - i)]); } } break; } case B_FLOATING_WINDOW_LOOK: case kLeftTitledWindowLook: { // top if (invalid.Intersects(fTopBorder)) { ComponentColors colors; _GetComponentColors(COMPONENT_TOP_BORDER, colors, fTopTab); for (int8 i = 0; i < 3; i++) { fDrawingEngine->StrokeLine(BPoint(r.left + i, r.top + i), BPoint(r.right - i, r.top + i), colors[i * 2]); } if (fTitleBarRect.IsValid() && fTopTab->look != kLeftTitledWindowLook) { // grey along the bottom of the tab // (overwrites "white" from frame) fDrawingEngine->StrokeLine( BPoint(fTitleBarRect.left + 2, fTitleBarRect.bottom + 1), BPoint(fTitleBarRect.right - 2, fTitleBarRect.bottom + 1), colors[2]); } } // left if (invalid.Intersects(fLeftBorder.InsetByCopy(0, -fBorderWidth))) { ComponentColors colors; _GetComponentColors(COMPONENT_LEFT_BORDER, colors, fTopTab); for (int8 i = 0; i < 3; i++) { fDrawingEngine->StrokeLine(BPoint(r.left + i, r.top + i), BPoint(r.left + i, r.bottom - i), colors[i * 2]); } if (fTopTab->look == kLeftTitledWindowLook && fTitleBarRect.IsValid()) { // grey along the right side of the tab // (overwrites "white" from frame) fDrawingEngine->StrokeLine( BPoint(fTitleBarRect.right + 1, fTitleBarRect.top + 2), BPoint(fTitleBarRect.right + 1, fTitleBarRect.bottom - 2), colors[2]); } } // bottom if (invalid.Intersects(fBottomBorder)) { ComponentColors colors; _GetComponentColors(COMPONENT_BOTTOM_BORDER, colors, fTopTab); for (int8 i = 0; i < 3; i++) { fDrawingEngine->StrokeLine(BPoint(r.left + i, r.bottom - i), BPoint(r.right - i, r.bottom - i), colors[(2 - i) == 2 ? 5 : (2 - i) * 2]); } } // right if (invalid.Intersects(fRightBorder.InsetByCopy(0, -fBorderWidth))) { ComponentColors colors; _GetComponentColors(COMPONENT_RIGHT_BORDER, colors, fTopTab); for (int8 i = 0; i < 3; i++) { fDrawingEngine->StrokeLine(BPoint(r.right - i, r.top + i), BPoint(r.right - i, r.bottom - i), colors[(2 - i) == 2 ? 5 : (2 - i) * 2]); } } break; } case B_BORDERED_WINDOW_LOOK: { // TODO: Draw the borders individually! ComponentColors colors; _GetComponentColors(COMPONENT_LEFT_BORDER, colors, fTopTab); fDrawingEngine->StrokeRect(r, colors[5]); break; } default: // don't draw a border frame break; } // Draw the resize knob if we're supposed to if (!(fTopTab->flags & B_NOT_RESIZABLE)) { r = fResizeRect; ComponentColors colors; _GetComponentColors(COMPONENT_RESIZE_CORNER, colors, fTopTab); switch ((int)fTopTab->look) { case B_DOCUMENT_WINDOW_LOOK: { if (!invalid.Intersects(r)) break; float x = r.right - 3; float y = r.bottom - 3; BRect bg(x - 13, y - 13, x, y); BGradientLinear gradient; gradient.SetStart(bg.LeftTop()); gradient.SetEnd(bg.RightBottom()); gradient.AddColor(colors[1], 0); gradient.AddColor(colors[2], 255); fDrawingEngine->FillRect(bg, gradient); fDrawingEngine->StrokeLine(BPoint(x - 15, y - 15), BPoint(x - 15, y - 2), colors[0]); fDrawingEngine->StrokeLine(BPoint(x - 14, y - 14), BPoint(x - 14, y - 1), colors[1]); fDrawingEngine->StrokeLine(BPoint(x - 15, y - 15), BPoint(x - 2, y - 15), colors[0]); fDrawingEngine->StrokeLine(BPoint(x - 14, y - 14), BPoint(x - 1, y - 14), colors[1]); if (fTopTab && !IsFocus(fTopTab)) break; static const rgb_color kWhite = (rgb_color){ 255, 255, 255, 255 }; for (int8 i = 1; i <= 4; i++) { for (int8 j = 1; j <= i; j++) { BPoint pt1(x - (3 * j) + 1, y - (3 * (5 - i)) + 1); BPoint pt2(x - (3 * j) + 2, y - (3 * (5 - i)) + 2); fDrawingEngine->StrokePoint(pt1, colors[0]); fDrawingEngine->StrokePoint(pt2, kWhite); } } break; } case B_TITLED_WINDOW_LOOK: case B_FLOATING_WINDOW_LOOK: case B_MODAL_WINDOW_LOOK: case kLeftTitledWindowLook: { if (!invalid.Intersects(BRect(fRightBorder.right - kBorderResizeLength, fBottomBorder.bottom - kBorderResizeLength, fRightBorder.right - 1, fBottomBorder.bottom - 1))) { break; } fDrawingEngine->StrokeLine(BPoint(fRightBorder.left, fBottomBorder.bottom - kBorderResizeLength), BPoint(fRightBorder.right - 1, fBottomBorder.bottom - kBorderResizeLength), colors[0]); fDrawingEngine->StrokeLine( BPoint(fRightBorder.right - kBorderResizeLength, fBottomBorder.top), BPoint(fRightBorder.right - kBorderResizeLength, fBottomBorder.bottom - 1), colors[0]); break; } default: // don't draw resize corner break; } } } /*! \brief Actually draws the tab This function is called when the tab itself needs drawn. Other items, like the window title or buttons, should not be drawn here. \param tab The \a tab to update. \param invalid The area of the \a tab to update. */ void BeDecorator::_DrawTab(Decorator::Tab* tab, BRect invalid) { STRACE(("_DrawTab(%.1f, %.1f, %.1f, %.1f)\n", invalid.left, invalid.top, invalid.right, invalid.bottom)); const BRect& tabRect = tab->tabRect; // If a window has a tab, this will draw it and any buttons which are // in it. if (!tabRect.IsValid() || !invalid.Intersects(tabRect)) return; ComponentColors colors; _GetComponentColors(COMPONENT_TAB, colors, tab); // outer frame fDrawingEngine->StrokeLine(tabRect.LeftTop(), tabRect.LeftBottom(), colors[COLOR_TAB_FRAME_LIGHT]); fDrawingEngine->StrokeLine(tabRect.LeftTop(), tabRect.RightTop(), colors[COLOR_TAB_FRAME_LIGHT]); if (tab->look != kLeftTitledWindowLook) { fDrawingEngine->StrokeLine(tabRect.RightTop(), tabRect.RightBottom(), colors[COLOR_TAB_FRAME_DARK]); } else { fDrawingEngine->StrokeLine(tabRect.LeftBottom(), tabRect.RightBottom(), colors[COLOR_TAB_FRAME_DARK]); } float tabBotton = tabRect.bottom; if (fTopTab != tab) tabBotton -= 1; // bevel fDrawingEngine->StrokeLine(BPoint(tabRect.left + 1, tabRect.top + 1), BPoint(tabRect.left + 1, tabBotton - (tab->look == kLeftTitledWindowLook ? 1 : 0)), colors[COLOR_TAB_BEVEL]); fDrawingEngine->StrokeLine(BPoint(tabRect.left + 1, tabRect.top + 1), BPoint(tabRect.right - (tab->look == kLeftTitledWindowLook ? 0 : 1), tabRect.top + 1), colors[COLOR_TAB_BEVEL]); if (tab->look != kLeftTitledWindowLook) { fDrawingEngine->StrokeLine(BPoint(tabRect.right - 1, tabRect.top + 2), BPoint(tabRect.right - 1, tabBotton), colors[COLOR_TAB_SHADOW]); } else { fDrawingEngine->StrokeLine( BPoint(tabRect.left + 2, tabRect.bottom - 1), BPoint(tabRect.right, tabRect.bottom - 1), colors[COLOR_TAB_SHADOW]); } // fill if (fTopTab->look != kLeftTitledWindowLook) { fDrawingEngine->FillRect(BRect(tabRect.left + 2, tabRect.top + 2, tabRect.right - 2, tabRect.bottom), colors[COLOR_TAB]); } else { fDrawingEngine->FillRect(BRect(tabRect.left + 2, tabRect.top + 2, tabRect.right, tabRect.bottom - 2), colors[COLOR_TAB]); } _DrawTitle(tab, tabRect); _DrawButtons(tab, invalid); } /*! \brief Actually draws the title The main tasks for this function are to ensure that the decorator draws the title only in its own area and drawing the title itself. Using B_OP_COPY for drawing the title is recommended because of the marked performance hit of the other drawing modes, but it is not a requirement. \param _tab The \a tab to update. \param r area of the title to update. */ void BeDecorator::_DrawTitle(Decorator::Tab* _tab, BRect r) { STRACE(("_DrawTitle(%f, %f, %f, %f)\n", r.left, r.top, r.right, r.bottom)); Decorator::Tab* tab = static_cast(_tab); const BRect& tabRect = tab->tabRect; const BRect& closeRect = tab->closeRect; const BRect& zoomRect = tab->zoomRect; ComponentColors colors; _GetComponentColors(COMPONENT_TAB, colors, tab); fDrawingEngine->SetDrawingMode(B_OP_OVER); fDrawingEngine->SetHighColor(colors[COLOR_TAB_TEXT]); fDrawingEngine->SetLowColor(colors[COLOR_TAB]); fDrawingEngine->SetFont(fDrawState.Font()); // figure out position of text font_height fontHeight; fDrawState.Font().GetHeight(fontHeight); BPoint titlePos; if (fTopTab->look != kLeftTitledWindowLook) { titlePos.x = closeRect.IsValid() ? closeRect.right + tab->textOffset : tabRect.left + tab->textOffset; titlePos.y = floorf(((tabRect.top + 2.0) + tabRect.bottom + fontHeight.ascent + fontHeight.descent) / 2.0 - fontHeight.descent + 0.5); } else { titlePos.x = floorf(((tabRect.left + 2.0) + tabRect.right + fontHeight.ascent + fontHeight.descent) / 2.0 - fontHeight.descent + 0.5); titlePos.y = zoomRect.IsValid() ? zoomRect.top - tab->textOffset : tabRect.bottom - tab->textOffset; } fDrawingEngine->SetFont(fDrawState.Font()); fDrawingEngine->DrawString(tab->truncatedTitle.String(), tab->truncatedTitleLength, titlePos); fDrawingEngine->SetDrawingMode(B_OP_COPY); } /*! \brief Actually draws the close button Unless a subclass has a particularly large button, it is probably unnecessary to check the update rectangle. \param _tab The \a tab to update. \param direct Draw without double buffering. \param rect The area of the button to update. */ void BeDecorator::_DrawClose(Decorator::Tab* _tab, bool direct, BRect rect) { STRACE(("_DrawClose(%f,%f,%f,%f)\n", rect.left, rect.top, rect.right, rect.bottom)); Decorator::Tab* tab = static_cast(_tab); int32 index = (tab->buttonFocus ? 0 : 1) + (tab->closePressed ? 0 : 2); ServerBitmap* bitmap = tab->closeBitmaps[index]; if (bitmap == NULL) { bitmap = _GetBitmapForButton(tab, COMPONENT_CLOSE_BUTTON, tab->closePressed, rect.IntegerWidth(), rect.IntegerHeight()); tab->closeBitmaps[index] = bitmap; } _DrawButtonBitmap(bitmap, direct, rect); } /*! \brief Actually draws the zoom button Unless a subclass has a particularly large button, it is probably unnecessary to check the update rectangle. \param _tab The \a tab to update. \param direct Draw without double buffering. \param rect The area of the button to update. */ void BeDecorator::_DrawZoom(Decorator::Tab* _tab, bool direct, BRect rect) { STRACE(("_DrawZoom(%f,%f,%f,%f)\n", rect.left, rect.top, rect.right, rect.bottom)); if (rect.IntegerWidth() < 1) return; Decorator::Tab* tab = static_cast(_tab); int32 index = (tab->buttonFocus ? 0 : 1) + (tab->zoomPressed ? 0 : 2); ServerBitmap* bitmap = tab->zoomBitmaps[index]; if (bitmap == NULL) { bitmap = _GetBitmapForButton(tab, COMPONENT_ZOOM_BUTTON, tab->zoomPressed, rect.IntegerWidth(), rect.IntegerHeight()); tab->zoomBitmaps[index] = bitmap; } _DrawButtonBitmap(bitmap, direct, rect); } void BeDecorator::_DrawMinimize(Decorator::Tab* tab, bool direct, BRect rect) { // This decorator doesn't have this button } void BeDecorator::_GetButtonSizeAndOffset(const BRect& tabRect, float* _offset, float* _size, float* _inset) const { float tabSize = fTopTab->look == kLeftTitledWindowLook ? tabRect.Width() : tabRect.Height(); *_offset = 5.0f; *_inset = 0.0f; *_size = std::max(0.0f, tabSize - 7.0f); } // #pragma mark - Private methods /*! \brief Draws a bevel around a rectangle. \param rect The rectangular area to draw in. \param down Whether or not the button is pressed down. \param light The light color to use. \param shadow The shadow color to use. */ void BeDecorator::_DrawBevelRect(DrawingEngine* engine, const BRect rect, bool down, rgb_color light, rgb_color shadow) { if (down) { BRect inner(rect.InsetByCopy(1.0f, 1.0f)); engine->StrokeLine(rect.LeftBottom(), rect.LeftTop(), shadow); engine->StrokeLine(rect.LeftTop(), rect.RightTop(), shadow); engine->StrokeLine(inner.LeftBottom(), inner.LeftTop(), shadow); engine->StrokeLine(inner.LeftTop(), inner.RightTop(), shadow); engine->StrokeLine(rect.RightTop(), rect.RightBottom(), light); engine->StrokeLine(rect.RightBottom(), rect.LeftBottom(), light); engine->StrokeLine(inner.RightTop(), inner.RightBottom(), light); engine->StrokeLine(inner.RightBottom(), inner.LeftBottom(), light); } else { BRect r1(rect); r1.left += 1.0f; r1.top += 1.0f; BRect r2(rect); r2.bottom -= 1.0f; r2.right -= 1.0f; engine->StrokeRect(r2, shadow); // inner dark box engine->StrokeRect(rect, shadow); // outer dark box engine->StrokeRect(r1, light); // light box } } /*! \brief Draws a framed rectangle with a gradient. \param rect The rectangular area to draw in. \param startColor The start color of the gradient. \param endColor The end color of the gradient. */ void BeDecorator::_DrawBlendedRect(DrawingEngine* engine, const BRect rect, bool down, rgb_color colorA, rgb_color colorB, rgb_color colorC, rgb_color colorD) { BRect fillRect(rect.InsetByCopy(1.0f, 1.0f)); BGradientLinear gradient; if (down) { gradient.SetStart(fillRect.RightBottom()); gradient.SetEnd(fillRect.LeftTop()); } else { gradient.SetStart(fillRect.LeftTop()); gradient.SetEnd(fillRect.RightBottom()); } gradient.AddColor(colorA, 0); gradient.AddColor(colorB, 95); gradient.AddColor(colorC, 159); gradient.AddColor(colorD, 255); engine->FillRect(fillRect, gradient); } void BeDecorator::_DrawButtonBitmap(ServerBitmap* bitmap, bool direct, BRect rect) { if (bitmap == NULL) return; bool copyToFrontEnabled = fDrawingEngine->CopyToFrontEnabled(); fDrawingEngine->SetCopyToFrontEnabled(direct); drawing_mode oldMode; fDrawingEngine->SetDrawingMode(B_OP_OVER, oldMode); fDrawingEngine->DrawBitmap(bitmap, rect.OffsetToCopy(0, 0), rect); fDrawingEngine->SetDrawingMode(oldMode); fDrawingEngine->SetCopyToFrontEnabled(copyToFrontEnabled); } ServerBitmap* BeDecorator::_GetBitmapForButton(Decorator::Tab* tab, Component item, bool down, int32 width, int32 height) { uint8* data; size_t size; size_t offset; // TODO: the list of shared bitmaps is never freed struct decorator_bitmap { Component item; bool down; int32 width; int32 height; rgb_color baseColor; rgb_color lightColor; UtilityBitmap* bitmap; decorator_bitmap* next; }; static BLocker sBitmapListLock("decorator lock", true); static decorator_bitmap* sBitmapList = NULL; // BeOS R5 colors // button: active: 255, 203, 0 inactive: 232, 232, 232 // light1: active: 255, 238, 0 inactive: 255, 255, 255 // light2: active: 255, 255, 26 inactive: 255, 255, 255 // shadow1: active: 235, 183, 0 inactive: 211, 211, 211 // shadow2 is a bit lighter on zoom than on close button ComponentColors colors; _GetComponentColors(item, colors, tab); const rgb_color buttonColor(colors[COLOR_BUTTON]); bool isGrayscale = buttonColor.red == buttonColor.green && buttonColor.green == buttonColor.blue; rgb_color buttonColorLight1(buttonColor); buttonColorLight1.red = std::min(255, buttonColor.red + 35), buttonColorLight1.green = std::min(255, buttonColor.green + 35), buttonColorLight1.blue = std::min(255, buttonColor.blue + (isGrayscale ? 35 : 0)); // greyscale color stays grayscale rgb_color buttonColorLight2(buttonColor); buttonColorLight2.red = std::min(255, buttonColor.red + 52), buttonColorLight2.green = std::min(255, buttonColor.green + 52), buttonColorLight2.blue = std::min(255, buttonColor.blue + 26); rgb_color buttonColorShadow1(buttonColor); buttonColorShadow1.red = std::max(0, buttonColor.red - 21), buttonColorShadow1.green = std::max(0, buttonColor.green - 21), buttonColorShadow1.blue = std::max(0, buttonColor.blue - 21); BAutolock locker(sBitmapListLock); // search our list for a matching bitmap // TODO: use a hash map instead? decorator_bitmap* current = sBitmapList; while (current) { if (current->item == item && current->down == down && current->width == width && current->height == height && current->baseColor == colors[COLOR_BUTTON] && current->lightColor == colors[COLOR_BUTTON_LIGHT]) { return current->bitmap; } current = current->next; } static BitmapDrawingEngine* sBitmapDrawingEngine = NULL; // didn't find any bitmap, create a new one if (sBitmapDrawingEngine == NULL) sBitmapDrawingEngine = new(std::nothrow) BitmapDrawingEngine(); if (sBitmapDrawingEngine == NULL || sBitmapDrawingEngine->SetSize(width, height) != B_OK) { return NULL; } BRect rect(0, 0, width - 1, height - 1); STRACE(("BeDecorator creating bitmap for %s %s at size %ldx%ld\n", item == COMPONENT_CLOSE_BUTTON ? "close" : "zoom", down ? "down" : "up", width, height)); switch (item) { case COMPONENT_CLOSE_BUTTON: { // BeOS R5 shadow2: active: 183, 131, 0 inactive: 160, 160, 160 rgb_color buttonColorShadow2(buttonColor); buttonColorShadow2.red = std::max(0, buttonColor.red - 72), buttonColorShadow2.green = std::max(0, buttonColor.green - 72), buttonColorShadow2.blue = std::max(0, buttonColor.blue - 72); // fill the background sBitmapDrawingEngine->FillRect(rect, buttonColor); // draw outer bevel _DrawBevelRect(sBitmapDrawingEngine, rect, tab->closePressed, buttonColorLight2, buttonColorShadow2); if (fCStatus != B_OK) { // If we ran out of memory while initializing bitmaps // fall back to a linear gradient. rect.InsetBy(1, 1); _DrawBlendedRect(sBitmapDrawingEngine, rect, tab->closePressed, buttonColorLight2, buttonColorLight1, buttonColor, buttonColorShadow1); break; } // inset by bevel rect.InsetBy(2, 2); // fill bg sBitmapDrawingEngine->FillRect(rect, buttonColorLight1); // treat background color as transparent sBitmapDrawingEngine->SetDrawingMode(B_OP_OVER); sBitmapDrawingEngine->SetLowColor(buttonColorLight1); if (tab->closePressed) { // Draw glint in bottom right, then combined inner and outer // shadow in top left. // Read the source bitmap in forward while writing the // destination in reverse to rotate the bitmap by 180°. data = fGlintBitmap->Bits(); size = sizeof(kGlintBits); for (size_t i = 0; i < size; i++) { offset = (size - 1 - i) * 4; if (kGlintBits[i] == 0) { // draw glint color data[offset + 0] = buttonColorLight2.blue; data[offset + 1] = buttonColorLight2.green; data[offset + 2] = buttonColorLight2.red; } else { // draw background color data[offset + 0] = buttonColorLight1.blue; data[offset + 1] = buttonColorLight1.green; data[offset + 2] = buttonColorLight1.red; } } // glint is 3x3 const BRect rightBottom(BRect(rect.right - 2, rect.bottom - 2, rect.right, rect.bottom)); sBitmapDrawingEngine->DrawBitmap(fGlintBitmap, fGlintBitmap->Bounds(), rightBottom); data = fCloseBitmap->Bits(); size = sizeof(kOuterShadowBits); for (size_t i = 0; i < size; i++) { offset = (size - 1 - i) * 4; if (kOuterShadowBits[i] == 0) { // draw outer shadow data[offset + 0] = buttonColorShadow1.blue; data[offset + 1] = buttonColorShadow1.green; data[offset + 2] = buttonColorShadow1.red; } else if (kInnerShadowBits[i] == 0) { // draw inner shadow data[offset + 0] = buttonColor.blue; data[offset + 1] = buttonColor.green; data[offset + 2] = buttonColor.red; } else { // draw background color data[offset + 0] = buttonColorLight1.blue; data[offset + 1] = buttonColorLight1.green; data[offset + 2] = buttonColorLight1.red; } } // shadow is 10x10 const BRect leftTop(rect.left, rect.top, rect.left + 9, rect.top + 9); sBitmapDrawingEngine->DrawBitmap(fCloseBitmap, fCloseBitmap->Bounds(), leftTop); } else { // draw glint, then draw combined outer and inner shadows data = fGlintBitmap->Bits(); size = sizeof(kGlintBits); for (size_t i = 0; i < size; i++) { offset = i * 4 + 0; if (kGlintBits[i] == 0) { // draw glint color data[offset + 0] = buttonColorLight2.blue; data[offset + 1] = buttonColorLight2.green; data[offset + 2] = buttonColorLight2.red; } else { // draw background color data[offset + 0] = buttonColorLight1.blue; data[offset + 1] = buttonColorLight1.green; data[offset + 2] = buttonColorLight1.red; } } // glint is 3x3 const BRect leftTop(rect.left, rect.top, rect.left + 2, rect.top + 2); sBitmapDrawingEngine->DrawBitmap(fGlintBitmap, fGlintBitmap->Bounds(), leftTop); data = fCloseBitmap->Bits(); size = sizeof(kOuterShadowBits); for (size_t i = 0; i < size; i++) { offset = i * 4 + 0; if (kOuterShadowBits[i] == 0) { // draw outer shadow data[offset + 0] = buttonColorShadow1.blue; data[offset + 1] = buttonColorShadow1.green; data[offset + 2] = buttonColorShadow1.red; } else if (kInnerShadowBits[i] == 0) { // draw inner shadow data[offset + 0] = buttonColor.blue; data[offset + 1] = buttonColor.green; data[offset + 2] = buttonColor.red; } else { // draw background color data[offset + 0] = buttonColorLight1.blue; data[offset + 1] = buttonColorLight1.green; data[offset + 2] = buttonColorLight1.red; } } // shadow is 10x10 const BRect rightBottom(BRect(rect.right - 9, rect.bottom - 9, rect.right, rect.bottom)); sBitmapDrawingEngine->DrawBitmap(fCloseBitmap, fCloseBitmap->Bounds(), rightBottom); } // restore drawing mode sBitmapDrawingEngine->SetDrawingMode(B_OP_COPY); break; } case COMPONENT_ZOOM_BUTTON: { // BeOS R5 shadow2: active: 210, 158, 0 inactive: 187, 187, 187 rgb_color buttonColorShadow2(buttonColor); buttonColorShadow2.red = std::max(0, buttonColor.red - 45), buttonColorShadow2.green = std::max(0, buttonColor.green - 45), buttonColorShadow2.blue = std::max(0, buttonColor.blue - 45); // fill the background sBitmapDrawingEngine->FillRect(rect, buttonColor); // big rect BRect bigRect(rect); bigRect.left += floorf(width * 3.0f / 14.0f); bigRect.top += floorf(height * 3.0f / 14.0f); // small rect BRect smallRect(rect); smallRect.right -= floorf(width * 5.0f / 14.0f); smallRect.bottom -= floorf(height * 5.0f / 14.0f); // draw big rect bevel _DrawBevelRect(sBitmapDrawingEngine, bigRect, tab->zoomPressed, buttonColorLight2, buttonColorShadow2); if (fCStatus != B_OK) { // If we ran out of memory while initializing bitmaps // fall back to a linear gradient. // already drew bigRect bevel, fill with linear gradient bigRect.InsetBy(1, 1); _DrawBlendedRect(sBitmapDrawingEngine, bigRect, tab->zoomPressed, buttonColorLight2, buttonColorLight1, buttonColor, buttonColorShadow1); // draw small rect bevel then fill with linear gradient _DrawBevelRect(sBitmapDrawingEngine, smallRect, tab->zoomPressed, buttonColorLight2, buttonColorShadow2); if (!tab->zoomPressed) { // undraw bottom left and top right corners sBitmapDrawingEngine->StrokePoint(smallRect.LeftBottom(), buttonColor); sBitmapDrawingEngine->StrokePoint(smallRect.RightTop(), buttonColor); } smallRect.InsetBy(1, 1); _DrawBlendedRect(sBitmapDrawingEngine, smallRect, tab->zoomPressed, buttonColorLight2, buttonColorLight1, buttonColor, buttonColorShadow1); break; } // inset past bevel bigRect.InsetBy(2, 2); // fill big rect bg sBitmapDrawingEngine->FillRect(bigRect, buttonColorLight1); // some elements are covered by the small rect // so only draw the parts that get shown if (tab->zoomPressed) { // draw glint // Read the source bitmap in forward while writing the // destination in reverse to rotate the bitmap by 180°. data = fGlintBitmap->Bits(); size = sizeof(kGlintBits); for (size_t i = 0; i < sizeof(kGlintBits); i++) { offset = (size - 1 - i) * 4; if (kGlintBits[i] == 0) { // draw glint data[offset + 0] = buttonColorLight2.blue; data[offset + 1] = buttonColorLight2.green; data[offset + 2] = buttonColorLight2.red; } else { // draw background color data[offset + 0] = buttonColorLight1.blue; data[offset + 1] = buttonColorLight1.green; data[offset + 2] = buttonColorLight1.red; } } // glint is 3x3 const BRect rightBottom(BRect(bigRect.right - 2, bigRect.bottom - 2, bigRect.right, bigRect.bottom)); sBitmapDrawingEngine->DrawBitmap(fGlintBitmap, fGlintBitmap->Bounds(), rightBottom); } else { // draw combined inner and outer shadow data = fBigZoomBitmap->Bits(); for (size_t i = 0; i < sizeof(kBigOuterShadowBits); i++) { offset = i * 4; if (kBigOuterShadowBits[i] == 0) { // draw outer shadow data[offset + 0] = buttonColorShadow1.blue; data[offset + 1] = buttonColorShadow1.green; data[offset + 2] = buttonColorShadow1.red; } else if (kBigInnerShadowBits[i] == 0) { // draw inner shadow data[offset + 0] = buttonColor.blue; data[offset + 1] = buttonColor.green; data[offset + 2] = buttonColor.red; } else { // draw background color data[offset + 0] = buttonColorLight1.blue; data[offset + 1] = buttonColorLight1.green; data[offset + 2] = buttonColorLight1.red; } } // shadow is 7x7 const BRect rightBottom(BRect(bigRect.right - 6, bigRect.bottom - 6, bigRect.right, bigRect.bottom)); sBitmapDrawingEngine->DrawBitmap(fBigZoomBitmap, fBigZoomBitmap->Bounds(), rightBottom); } sBitmapDrawingEngine->SetDrawingMode(B_OP_COPY); // draw small rect bevel _DrawBevelRect(sBitmapDrawingEngine, smallRect, tab->zoomPressed, buttonColorLight2, buttonColorShadow2); if (!tab->zoomPressed) { // undraw bottom left and top right corners sBitmapDrawingEngine->StrokePoint(smallRect.LeftBottom(), buttonColor); sBitmapDrawingEngine->StrokePoint(smallRect.RightTop(), buttonColor); } // inset past bevel smallRect.InsetBy(2, 2); // fill small rect bg sBitmapDrawingEngine->FillRect(smallRect, buttonColorLight1); // treat background color as transparent sBitmapDrawingEngine->SetDrawingMode(B_OP_OVER); sBitmapDrawingEngine->SetLowColor(buttonColorLight1); // draw small bitmap data = fSmallZoomBitmap->Bits(); size = sizeof(kSmallOuterShadowBits); if (tab->zoomPressed) { // draw combined inner and outer shadow // Read the source bitmap in forward while writing the // destination in reverse to rotate the bitmap by 180°. for (size_t i = 0; i < size; i++) { offset = (size - 1 - i) * 4; if (kSmallOuterShadowBits[i] == 0) { // draw outer shadow data[offset + 0] = buttonColorShadow1.blue; data[offset + 1] = buttonColorShadow1.green; data[offset + 2] = buttonColorShadow1.red; } else if (kSmallInnerShadowBits[i] == 0) { // draw inner shadow data[offset + 0] = buttonColor.blue; data[offset + 1] = buttonColor.green; data[offset + 2] = buttonColor.red; } else { // draw background color data[offset + 0] = buttonColorLight1.blue; data[offset + 1] = buttonColorLight1.green; data[offset + 2] = buttonColorLight1.red; } } // shadow is 5x5 const BRect smallLeftTop(BRect(smallRect.left, smallRect.top, smallRect.left + 4, smallRect.top + 4)); sBitmapDrawingEngine->DrawBitmap(fSmallZoomBitmap, fSmallZoomBitmap->Bounds(), smallLeftTop); } else { // draw combined inner and outer shadow for (size_t i = 0; i < size; i++) { offset = i * 4; if (kSmallOuterShadowBits[i] == 0) { // draw outer shadow data[offset + 0] = buttonColorShadow1.blue; data[offset + 1] = buttonColorShadow1.green; data[offset + 2] = buttonColorShadow1.red; } else if (kSmallInnerShadowBits[i] == 0) { // draw inner shadow data[offset + 0] = buttonColor.blue; data[offset + 1] = buttonColor.green; data[offset + 2] = buttonColor.red; } else { // draw background color data[offset + 0] = buttonColorLight1.blue; data[offset + 1] = buttonColorLight1.green; data[offset + 2] = buttonColorLight1.red; } } // shadow is 5x5 const BRect smallRightBottom(BRect(smallRect.right - 4, smallRect.bottom - 4, smallRect.right, smallRect.bottom)); sBitmapDrawingEngine->DrawBitmap(fSmallZoomBitmap, fSmallZoomBitmap->Bounds(), smallRightBottom); } // draw glint last (single pixel) sBitmapDrawingEngine->StrokePoint(tab->zoomPressed ? smallRect.RightBottom() : smallRect.LeftTop(), buttonColorLight2); // restore drawing mode sBitmapDrawingEngine->SetDrawingMode(B_OP_COPY); break; } default: break; } UtilityBitmap* bitmap = sBitmapDrawingEngine->ExportToBitmap(width, height, B_RGB32); if (bitmap == NULL) return NULL; // bitmap ready, put it into the list decorator_bitmap* entry = new(std::nothrow) decorator_bitmap; if (entry == NULL) { delete bitmap; return NULL; } entry->item = item; entry->down = down; entry->width = width; entry->height = height; entry->bitmap = bitmap; entry->baseColor = colors[COLOR_BUTTON]; entry->lightColor = colors[COLOR_BUTTON_LIGHT]; entry->next = sBitmapList; sBitmapList = entry; return bitmap; } ServerBitmap* BeDecorator::_CreateTemporaryBitmap(BRect bounds) const { UtilityBitmap* bitmap = new(std::nothrow) UtilityBitmap(bounds, B_RGB32, 0); if (bitmap == NULL) return NULL; if (!bitmap->IsValid()) { delete bitmap; return NULL; } memset(bitmap->Bits(), 0, bitmap->BitsLength()); // background opacity is 0 return bitmap; } void BeDecorator::_GetComponentColors(Component component, ComponentColors _colors, Decorator::Tab* tab) { // get the highlight for our component Region region = REGION_NONE; switch (component) { case COMPONENT_TAB: region = REGION_TAB; break; case COMPONENT_CLOSE_BUTTON: region = REGION_CLOSE_BUTTON; break; case COMPONENT_ZOOM_BUTTON: region = REGION_ZOOM_BUTTON; break; case COMPONENT_LEFT_BORDER: region = REGION_LEFT_BORDER; break; case COMPONENT_RIGHT_BORDER: region = REGION_RIGHT_BORDER; break; case COMPONENT_TOP_BORDER: region = REGION_TOP_BORDER; break; case COMPONENT_BOTTOM_BORDER: region = REGION_BOTTOM_BORDER; break; case COMPONENT_RESIZE_CORNER: region = REGION_RIGHT_BOTTOM_CORNER; break; } return GetComponentColors(component, RegionHighlight(region), _colors, tab); } extern "C" DecorAddOn* (instantiate_decor_addon)(image_id id, const char* name) { return new (std::nothrow)BeDecorAddOn(id, name); }