/* * Copyright 2009-2014 Haiku, Inc. All rights reserved. * Distributed under the terms of the MIT License. * * Authors: * DarkWyrm, bpmagic@columbus.rr.com * Adrien Destugues, pulkomandy@gmail.com * John Scipione, jscipione@gmail.com */ /*! Decorator resembling Windows 95 */ #include "WinDecorator.h" #include #include #include #include #include #include "DesktopSettings.h" #include "DrawingEngine.h" #include "PatternHandler.h" #include "RGBColor.h" //#define DEBUG_DECORATOR #ifdef DEBUG_DECORATOR # define STRACE(x) printf x #else # define STRACE(x) ; #endif WinDecorAddOn::WinDecorAddOn(image_id id, const char* name) : DecorAddOn(id, name) { } Decorator* WinDecorAddOn::_AllocateDecorator(DesktopSettings& settings, BRect rect, Desktop* desktop) { return new (std::nothrow)WinDecorator(settings, rect, desktop); } WinDecorator::WinDecorator(DesktopSettings& settings, BRect frame, Desktop* desktop) : SATDecorator(settings, frame, desktop), // common colors to both focus and non focus state fFrameHighColor((rgb_color){ 255, 255, 255, 255 }), fFrameMidColor((rgb_color){ 216, 216, 216, 255 }), fFrameLowColor((rgb_color){ 110, 110, 110, 255 }), fFrameLowerColor((rgb_color){ 0, 0, 0, 255 }), // state based colors fFocusTabColor(settings.UIColor(B_WINDOW_TAB_COLOR)), fFocusTextColor(settings.UIColor(B_WINDOW_TEXT_COLOR)), fNonFocusTabColor(settings.UIColor(B_WINDOW_INACTIVE_TAB_COLOR)), fNonFocusTextColor(settings.UIColor(B_WINDOW_INACTIVE_TEXT_COLOR)) { STRACE(("WinDecorator:\n")); STRACE(("\tFrame (%.1f,%.1f,%.1f,%.1f)\n", frame.left, frame.top, frame.right, frame.bottom)); } WinDecorator::~WinDecorator() { STRACE(("~WinDecorator()\n")); } /*! \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 WinDecorator::Draw(BRect updateRect) { STRACE(("WinDecorator::Draw(BRect updateRect): ")); updateRect.PrintToStream(); // We need to draw a few things: the tab, the resize knob, the borders, // and the buttons fDrawingEngine->SetDrawState(&fDrawState); _DrawFrame(updateRect & fBorderRect); _DrawTabs(updateRect & fTitleBarRect); } //! Forces a complete decorator update void WinDecorator::Draw() { STRACE(("WinDecorator: Draw()")); // Easy way to draw everything - no worries about drawing only certain // things fDrawingEngine->SetDrawState(&fDrawState); _DrawFrame(fBorderRect); _DrawTabs(fTitleBarRect); } // TODO : add GetSizeLimits Decorator::Region WinDecorator::RegionAt(BPoint where, int32& tabIndex) const { tabIndex = -1; for (int32 i = 0; i < fTabList.CountItems(); i++) { Decorator::Tab* tab = fTabList.ItemAt(i); if (tab->minimizeRect.Contains(where)) { tabIndex = i; return REGION_MINIMIZE_BUTTON; } } // Let the base class version identify hits of the buttons and the tab. Region region = Decorator::RegionAt(where, tabIndex); 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 (!(fTopTab->flags & B_NOT_RESIZABLE) && (fTopTab->look == B_TITLED_WINDOW_LOOK || fTopTab->look == B_FLOATING_WINDOW_LOOK || fTopTab->look == B_MODAL_WINDOW_LOOK) && fBorderRect.Contains(where) && !fFrame.Contains(where)) { return REGION_BOTTOM_BORDER; // TODO Determine the actual border! } return REGION_NONE; } bool WinDecorator::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/minimize/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_MINIMIZE_BUTTON: if (highlight != RegionHighlight(region)) { memset(&tab->minimizeBitmaps, 0, sizeof(tab->minimizeBitmaps)); } 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 WinDecorator::_DoLayout() { STRACE(("WinDecorator()::_DoLayout()\n")); bool hasTab = false; fBorderRect = fFrame; switch ((int)fTopTab->look) { case B_MODAL_WINDOW_LOOK: fBorderWidth = 4; break; case B_TITLED_WINDOW_LOOK: case B_DOCUMENT_WINDOW_LOOK: hasTab = true; fBorderWidth = 4; break; case B_FLOATING_WINDOW_LOOK: fBorderWidth = 0; hasTab = true; break; case B_BORDERED_WINDOW_LOOK: fBorderWidth = 1; break; default: fBorderWidth = 0; break; } fBorderRect.InsetBy(-fBorderWidth, -fBorderWidth); if (hasTab) { font_height fontHeight; fDrawState.Font().GetHeight(fontHeight); float tabSize = ceilf(fontHeight.ascent + fontHeight.descent + 4.0); if (tabSize < 20) tabSize = 20; fBorderRect.top -= tabSize; fTitleBarRect.Set(fFrame.left - 1, fFrame.top - tabSize, ((fFrame.right - fFrame.left) < 32.0 ? fFrame.left + 32.0 : fFrame.right) + 1, fFrame.top - 1); for (int32 i = 0; i < fTabList.CountItems(); i++) { Decorator::Tab* tab = fTabList.ItemAt(i); tab->tabRect = fTitleBarRect; const float buttonsInset = 3; tab->zoomRect = tab->tabRect; tab->zoomRect.top += buttonsInset; tab->zoomRect.right -= buttonsInset; tab->zoomRect.bottom -= buttonsInset; tab->zoomRect.left = tab->zoomRect.right - tabSize + buttonsInset; tab->closeRect = tab->zoomRect; tab->zoomRect.OffsetBy(0 - tab->zoomRect.Width() - 3, 0); tab->minimizeRect = tab->zoomRect; tab->minimizeRect.OffsetBy(0 - tab->zoomRect.Width() - 1, 0); } } else { 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); tab->closeRect.Set(0.0, 0.0, -1.0, -1.0); tab->zoomRect.Set(0.0, 0.0, -1.0, -1.0); tab->minimizeRect.Set(0.0, 0.0, -1.0, -1.0); } } } void WinDecorator::_DrawFrame(BRect rect) { if (fTopTab->look == B_NO_BORDER_WINDOW_LOOK) return; if (fBorderRect == fFrame) return; BRect r = fBorderRect; fDrawingEngine->SetHighColor(fFrameLowerColor); fDrawingEngine->StrokeRect(r); if (fTopTab->look == B_BORDERED_WINDOW_LOOK) return; BPoint pt; pt = r.RightTop(); pt.x--; fDrawingEngine->StrokeLine(r.LeftTop(), pt, fFrameMidColor); pt = r.LeftBottom(); pt.y--; fDrawingEngine->StrokeLine(r.LeftTop(), pt, fFrameMidColor); fDrawingEngine->StrokeLine(r.RightTop(), r.RightBottom(), fFrameLowerColor); fDrawingEngine->StrokeLine(r.LeftBottom(), r.RightBottom(), fFrameLowerColor); r.InsetBy(1, 1); pt = r.RightTop(); pt.x--; fDrawingEngine->StrokeLine(r.LeftTop(),pt,fFrameHighColor); pt = r.LeftBottom(); pt.y--; fDrawingEngine->StrokeLine(r.LeftTop(),pt,fFrameHighColor); fDrawingEngine->StrokeLine(r.RightTop(), r.RightBottom(), fFrameLowColor); fDrawingEngine->StrokeLine(r.LeftBottom(), r.RightBottom(), fFrameLowColor); r.InsetBy(1, 1); fDrawingEngine->StrokeRect(r, fFrameMidColor); r.InsetBy(1, 1); fDrawingEngine->StrokeRect(r, fFrameMidColor); } /*! \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 rect The area of the \a tab to update. */ void WinDecorator::_DrawTab(Decorator::Tab* tab, BRect rect) { const BRect& tabRect = tab->tabRect; // If a window has a tab, this will draw it and any buttons in it. if (!tabRect.IsValid() || !rect.Intersects(tabRect) || fTopTab->look == B_NO_BORDER_WINDOW_LOOK) { return; } fDrawingEngine->FillRect(tabRect & rect, fTabColor); _DrawTitle(tab, tabRect); // Draw the buttons if we're supposed to // TODO : we should still draw the buttons if they are disabled, but grey them out _DrawButtons(tab, rect); } /*! \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 rect area of the title to update. */ void WinDecorator::_DrawTitle(Decorator::Tab* tab, BRect rect) { const BRect& tabRect = tab->tabRect; const BRect& minimizeRect = tab->minimizeRect; const BRect& zoomRect = tab->zoomRect; const BRect& closeRect = tab->closeRect; fDrawingEngine->SetHighColor(fTextColor); fDrawingEngine->SetLowColor(IsFocus(tab) ? fFocusTabColor : fNonFocusTabColor); tab->truncatedTitle = Title(tab); fDrawState.Font().TruncateString(&tab->truncatedTitle, B_TRUNCATE_END, ((minimizeRect.IsValid() ? minimizeRect.left : zoomRect.IsValid() ? zoomRect.left : closeRect.IsValid() ? closeRect.left : tabRect.right) - 5) - (tabRect.left + 5)); tab->truncatedTitleLength = tab->truncatedTitle.Length(); fDrawingEngine->SetFont(fDrawState.Font()); font_height fontHeight; fDrawState.Font().GetHeight(fontHeight); BPoint titlePos; titlePos.x = tabRect.left + 5; titlePos.y = floorf(((tabRect.top + 2.0) + tabRect.bottom + fontHeight.ascent + fontHeight.descent) / 2.0 - fontHeight.descent + 0.5); fDrawingEngine->DrawString(tab->truncatedTitle, tab->truncatedTitleLength, titlePos); } void WinDecorator::_DrawButtons(Decorator::Tab* tab, const BRect& invalid) { if ((tab->flags & B_NOT_MINIMIZABLE) == 0 && invalid.Intersects(tab->minimizeRect)) { _DrawMinimize(tab, false, tab->minimizeRect); } if ((tab->flags & B_NOT_ZOOMABLE) == 0 && invalid.Intersects(tab->zoomRect)) { _DrawZoom(tab, false, tab->zoomRect); } if ((tab->flags & B_NOT_CLOSABLE) == 0 && invalid.Intersects(tab->closeRect)) { _DrawClose(tab, false, tab->closeRect); } } /*! \brief Actually draws the minimize 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 WinDecorator::_DrawMinimize(Decorator::Tab* tab, bool direct, BRect rect) { // Just like DrawZoom, but for a Minimize button _DrawBeveledRect(rect, tab->minimizePressed); fDrawingEngine->SetHighColor(fTextColor); BRect minimizeBox(rect.left + 5, rect.bottom - 4, rect.right - 5, rect.bottom - 3); if (true) minimizeBox.OffsetBy(1, 1); fDrawingEngine->SetHighColor(RGBColor(0, 0, 0)); fDrawingEngine->StrokeRect(minimizeBox); } /*! \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 WinDecorator::_DrawZoom(Decorator::Tab* tab, bool direct, BRect rect) { _DrawBeveledRect(rect, tab->zoomPressed); // Draw the Zoom box BRect zoomBox(rect); zoomBox.InsetBy(2, 2); zoomBox.InsetBy(1, 0); zoomBox.bottom--; zoomBox.right--; if (true) zoomBox.OffsetBy(1, 1); fDrawingEngine->SetHighColor(RGBColor(0,0,0)); fDrawingEngine->StrokeRect(zoomBox); zoomBox.InsetBy(1, 1); fDrawingEngine->StrokeLine(zoomBox.LeftTop(), zoomBox.RightTop()); } /*! \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 WinDecorator::_DrawClose(Decorator::Tab* tab, bool direct, BRect rect) { STRACE(("_DrawClose(%f, %f, %f, %f)\n", rect.left, rect.top, rect.right, rect.bottom)); // Just like DrawZoom, but for a close button _DrawBeveledRect(rect, tab->closePressed); // Draw the X BRect closeBox(rect); closeBox.InsetBy(4, 4); closeBox.right--; closeBox.top--; if (true) closeBox.OffsetBy(1, 1); fDrawingEngine->SetHighColor(RGBColor(0, 0, 0)); fDrawingEngine->StrokeLine(closeBox.LeftTop(), closeBox.RightBottom()); fDrawingEngine->StrokeLine(closeBox.RightTop(), closeBox.LeftBottom()); closeBox.OffsetBy(1, 0); fDrawingEngine->StrokeLine(closeBox.LeftTop(), closeBox.RightBottom()); fDrawingEngine->StrokeLine(closeBox.RightTop(), closeBox.LeftBottom()); } void WinDecorator::_SetTitle(Decorator::Tab* tab, const char* string, BRegion* updateRegion) { // TODO we could be much smarter about the update region BRect rect = TabRect(tab); if (updateRegion == NULL) return; BRect updatedRect = TabRect(tab); if (rect.left > updatedRect.left) rect.left = updatedRect.left; if (rect.right < updatedRect.right) rect.right = updatedRect.right; updateRegion->Include(rect); } void WinDecorator::_SetFocus(Decorator::Tab* tab) { // TODO stub // SetFocus() performs necessary duties for color swapping and // other things when a window is deactivated or activated. if (IsFocus(tab)) { fTabColor = fFocusTabColor; fTextColor = fFocusTextColor; } else { fTabColor = fNonFocusTabColor; fTextColor = fNonFocusTextColor; } } void WinDecorator::_MoveBy(BPoint offset) { // Move all tab rectangles over for (int32 i = 0; i < fTabList.CountItems(); i++) { Decorator::Tab* tab = fTabList.ItemAt(i); tab->zoomRect.OffsetBy(offset); tab->closeRect.OffsetBy(offset); tab->minimizeRect.OffsetBy(offset); tab->tabRect.OffsetBy(offset); } // Move all internal rectangles over fFrame.OffsetBy(offset); fTitleBarRect.OffsetBy(offset); fResizeRect.OffsetBy(offset); fBorderRect.OffsetBy(offset); } void WinDecorator::_ResizeBy(BPoint offset, BRegion* dirty) { // Move all internal rectangles the appropriate amount fFrame.right += offset.x; fFrame.bottom += offset.y; fTitleBarRect.right += offset.x; fTitleBarRect.bottom += offset.y; fResizeRect.OffsetBy(offset); fBorderRect.right += offset.x; fBorderRect.bottom += offset.y; for (int32 i = 0; i < fTabList.CountItems(); i++) { Decorator::Tab* tab = fTabList.ItemAt(i); tab->tabRect.right += offset.x; if (dirty != NULL) dirty->Include(tab->tabRect); //tab->zoomRect.right += offset.x; //tab->closeRect.right += offset.x; //tab->minimizeRect.right += offset.x; } if (dirty != NULL) dirty->Include(fBorderRect); // TODO probably some other layouting stuff here _DoLayout(); } // TODO : _SetSettings Decorator::Tab* WinDecorator::_AllocateNewTab() { Decorator::Tab* tab = new(std::nothrow) Decorator::Tab; if (tab == NULL) return NULL; // Set appropriate colors based on the current focus value. In this case, // each decorator defaults to not having the focus. _SetFocus(tab); return tab; } bool WinDecorator::_AddTab(DesktopSettings& settings, int32 index, BRegion* updateRegion) { _UpdateFont(settings); _DoLayout(); if (updateRegion != NULL) updateRegion->Include(fTitleBarRect); return true; } bool WinDecorator::_RemoveTab(int32 index, BRegion* updateRegion) { BRect oldTitle = fTitleBarRect; _DoLayout(); if (updateRegion != NULL) { updateRegion->Include(oldTitle); updateRegion->Include(fTitleBarRect); } return true; } bool WinDecorator::_MoveTab(int32 from, int32 to, bool isMoving, BRegion* updateRegion) { // TODO stub return false; } void WinDecorator::_GetFootprint(BRegion* region) { // 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; region->MakeEmpty(); if (fTopTab->look == B_NO_BORDER_WINDOW_LOOK) return; region->Set(fBorderRect); for (int32 i = 0; i < fTabList.CountItems(); i++) { Decorator::Tab* tab = fTabList.ItemAt(i); region->Include(tab->tabRect); } region->Exclude(fFrame); } void WinDecorator::_UpdateFont(DesktopSettings& settings) { ServerFont font; if (fTopTab->look == B_FLOATING_WINDOW_LOOK) settings.GetDefaultPlainFont(font); else settings.GetDefaultBoldFont(font); font.SetFlags(B_FORCE_ANTIALIASING); font.SetSpacing(B_STRING_SPACING); fDrawState.SetFont(font); } void WinDecorator::_DrawBeveledRect(BRect r, bool down) { RGBColor higher; RGBColor high; RGBColor mid; RGBColor low; RGBColor lower; if (down) { lower.SetColor(255,255,255); low.SetColor(216,216,216); mid.SetColor(192,192,192); high.SetColor(128,128,128); higher.SetColor(0,0,0); } else { higher.SetColor(255,255,255); high.SetColor(216,216,216); mid.SetColor(192,192,192); low.SetColor(128,128,128); lower.SetColor(0,0,0); } BRect rect(r); BPoint point; // Top highlight fDrawingEngine->SetHighColor(higher); fDrawingEngine->StrokeLine(rect.LeftTop(), rect.RightTop()); // Left highlight fDrawingEngine->StrokeLine(rect.LeftTop(), rect.LeftBottom()); // Right shading point = rect.RightTop(); point.y++; fDrawingEngine->StrokeLine(point, rect.RightBottom(), lower); // Bottom shading point = rect.LeftBottom(); point.x++; fDrawingEngine->StrokeLine(point, rect.RightBottom(), lower); rect.InsetBy(1,1); fDrawingEngine->SetHighColor(high); // Top inside highlight fDrawingEngine->StrokeLine(rect.LeftTop(), rect.RightTop()); // Left inside highlight fDrawingEngine->StrokeLine(rect.LeftTop(), rect.LeftBottom()); // Right inside shading point = rect.RightTop(); point.y++; fDrawingEngine->StrokeLine(point, rect.RightBottom(), low); // Bottom inside shading point = rect.LeftBottom(); point.x++; fDrawingEngine->StrokeLine(point, rect.RightBottom(), low); rect.InsetBy(1,1); fDrawingEngine->FillRect(rect, mid); } // #pragma mark - DecorAddOn extern "C" DecorAddOn* instantiate_decor_addon(image_id id, const char* name) { return new (std::nothrow)WinDecorAddOn(id, name); }