/* * Copyright 2006, Haiku. * Distributed under the terms of the MIT License. * * Authors: * Stephan Aßmus */ #include "IconButton.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include using std::nothrow; // constructor IconButton::IconButton(const char* name, uint32 id, const char* label, BMessage* message, BHandler* target) : BView(BRect(0.0, 0.0, 10.0, 10.0), name, B_FOLLOW_NONE, B_WILL_DRAW), BInvoker(message, target), fButtonState(STATE_ENABLED), fID(id), fNormalBitmap(NULL), fDisabledBitmap(NULL), fClickedBitmap(NULL), fDisabledClickedBitmap(NULL), fLabel(label), fTargetCache(target) { SetLowColor(ui_color(B_PANEL_BACKGROUND_COLOR)); SetViewColor(B_TRANSPARENT_32_BIT); } // destructor IconButton::~IconButton() { _DeleteBitmaps(); } // MessageReceived void IconButton::MessageReceived(BMessage* message) { switch (message->what) { default: BView::MessageReceived(message); break; } } // AttachedToWindow void IconButton::AttachedToWindow() { SetTarget(fTargetCache); if (!Target()) { SetTarget(Window()); } } // Draw void IconButton::Draw(BRect area) { rgb_color background = LowColor(); if (MView* parent = dynamic_cast(Parent())) background = parent->getcolor(); rgb_color lightShadow, shadow, darkShadow, light; BRect r(Bounds()); BBitmap* bitmap = fNormalBitmap; // adjust colors and bitmap according to flags if (IsEnabled()) { lightShadow = tint_color(background, B_DARKEN_1_TINT); shadow = tint_color(background, B_DARKEN_2_TINT); darkShadow = tint_color(background, B_DARKEN_4_TINT); light = tint_color(background, B_LIGHTEN_MAX_TINT); SetHighColor(0, 0, 0, 255); } else { lightShadow = tint_color(background, 1.11); shadow = tint_color(background, B_DARKEN_1_TINT); darkShadow = tint_color(background, B_DARKEN_2_TINT); light = tint_color(background, B_LIGHTEN_2_TINT); bitmap = fDisabledBitmap; SetHighColor(tint_color(background, B_DISABLED_LABEL_TINT)); } if (_HasFlags(STATE_PRESSED) || _HasFlags(STATE_FORCE_PRESSED)) { if (IsEnabled()) { // background = tint_color(background, B_DARKEN_2_TINT); // background = tint_color(background, B_LIGHTEN_1_TINT); background = tint_color(background, B_DARKEN_1_TINT); bitmap = fClickedBitmap; } else { // background = tint_color(background, B_DARKEN_1_TINT); // background = tint_color(background, (B_NO_TINT + B_LIGHTEN_1_TINT) / 2.0); background = tint_color(background, (B_NO_TINT + B_DARKEN_1_TINT) / 2.0); bitmap = fDisabledClickedBitmap; } // background SetLowColor(background); r.InsetBy(2.0, 2.0); StrokeLine(r.LeftBottom(), r.LeftTop(), B_SOLID_LOW); StrokeLine(r.LeftTop(), r.RightTop(), B_SOLID_LOW); r.InsetBy(-2.0, -2.0); } // draw frame only if tracking if (DrawBorder()) { if (_HasFlags(STATE_PRESSED) || _HasFlags(STATE_FORCE_PRESSED)) DrawPressedBorder(r, background, shadow, darkShadow, lightShadow, light); else DrawNormalBorder(r, background, shadow, darkShadow, lightShadow, light); r.InsetBy(2.0, 2.0); } else _DrawFrame(r, background, background, background, background); float width = Bounds().Width(); float height = Bounds().Height(); // bitmap BRegion originalClippingRegion; if (bitmap && bitmap->IsValid()) { float x = floorf((width - bitmap->Bounds().Width()) / 2.0 + 0.5); float y = floorf((height - bitmap->Bounds().Height()) / 2.0 + 0.5); BPoint point(x, y); if (_HasFlags(STATE_PRESSED) || _HasFlags(STATE_FORCE_PRESSED)) point += BPoint(1.0, 1.0); if (bitmap->ColorSpace() == B_RGBA32 || bitmap->ColorSpace() == B_RGBA32_BIG) { FillRect(r, B_SOLID_LOW); SetDrawingMode(B_OP_ALPHA); SetBlendingMode(B_PIXEL_ALPHA, B_ALPHA_OVERLAY); } DrawBitmap(bitmap, point); // constrain clipping region BRegion region= originalClippingRegion; GetClippingRegion(®ion); region.Exclude(bitmap->Bounds().OffsetByCopy(point)); ConstrainClippingRegion(®ion); } // background SetDrawingMode(B_OP_COPY); FillRect(r, B_SOLID_LOW); ConstrainClippingRegion(&originalClippingRegion); // label if (fLabel.CountChars() > 0) { SetDrawingMode(B_OP_COPY); font_height fh; GetFontHeight(&fh); float y = Bounds().bottom - 4.0; y -= fh.descent; float x = (width - StringWidth(fLabel.String())) / 2.0; DrawString(fLabel.String(), BPoint(x, y)); } } // MouseDown void IconButton::MouseDown(BPoint where) { if (IsValid()) { if (_HasFlags(STATE_ENABLED)/* && !_HasFlags(STATE_FORCE_PRESSED)*/) { if (Bounds().Contains(where)) { SetMouseEventMask(B_POINTER_EVENTS, B_LOCK_WINDOW_FOCUS); _AddFlags(STATE_PRESSED | STATE_TRACKING); } else { _ClearFlags(STATE_PRESSED | STATE_TRACKING); } } } } // MouseUp void IconButton::MouseUp(BPoint where) { if (IsValid()) { // if (!_HasFlags(STATE_FORCE_PRESSED)) { if (_HasFlags(STATE_ENABLED) && _HasFlags(STATE_PRESSED) && Bounds().Contains(where)) Invoke(); else if (Bounds().Contains(where)) _AddFlags(STATE_INSIDE); _ClearFlags(STATE_PRESSED | STATE_TRACKING); // } } } // MouseMoved void IconButton::MouseMoved(BPoint where, uint32 transit, const BMessage* message) { if (IsValid()) { uint32 buttons = 0; Window()->CurrentMessage()->FindInt32("buttons", (int32*)&buttons); // catch a mouse up event that we might have missed if (!buttons && _HasFlags(STATE_PRESSED)) { MouseUp(where); return; } if (buttons && !_HasFlags(STATE_TRACKING)) return; if ((transit == B_INSIDE_VIEW || transit == B_ENTERED_VIEW) && _HasFlags(STATE_ENABLED)) _AddFlags(STATE_INSIDE); else _ClearFlags(STATE_INSIDE); if (_HasFlags(STATE_TRACKING)) { if (Bounds().Contains(where)) _AddFlags(STATE_PRESSED); else _ClearFlags(STATE_PRESSED); } } } // GetPreferredSize void IconButton::GetPreferredSize(float* width, float* height) { layoutprefs(); if (width) *width = mpm.mini.x; if (height) *height = mpm.mini.y; } // Invoke status_t IconButton::Invoke(BMessage* message) { if (!message) message = Message(); if (message) { BMessage clone(*message); clone.AddInt64("be:when", system_time()); clone.AddPointer("be:source", (BView*)this); clone.AddInt32("be:value", Value()); clone.AddInt32("id", ID()); return BInvoker::Invoke(&clone); } return BInvoker::Invoke(message); } #define MIN_SPACE 15.0 // layoutprefs minimax IconButton::layoutprefs() { float minWidth = 0.0; float minHeight = 0.0; if (IsValid()) { minWidth += fNormalBitmap->Bounds().IntegerWidth() + 1.0; minHeight += fNormalBitmap->Bounds().IntegerHeight() + 1.0; } else { minWidth += MIN_SPACE; minHeight += MIN_SPACE; } if (minWidth < MIN_SPACE) minWidth = MIN_SPACE; if (minHeight < MIN_SPACE) minHeight = MIN_SPACE; if (fLabel.CountChars() > 0) { font_height fh; GetFontHeight(&fh); minHeight += ceilf(fh.ascent + fh.descent) + 4.0; minWidth += StringWidth(fLabel.String()) + 4.0; } mpm.mini.x = minWidth + 4.0; // mpm.maxi.x = 10000.0 + 4.0; mpm.maxi.x = minWidth + 4.0; mpm.mini.y = minHeight + 4.0; // mpm.maxi.y = 10000.0 + 4.0; mpm.maxi.y = minHeight + 4.0; mpm.weight = 0.0; return mpm; } // layout BRect IconButton::layout(BRect rect) { MoveTo(rect.LeftTop()); ResizeTo(rect.Width(), rect.Height()); return Frame(); } // SetPressed void IconButton::SetPressed(bool pressed) { if (pressed) _AddFlags(STATE_FORCE_PRESSED); else _ClearFlags(STATE_FORCE_PRESSED); } // IsPressed bool IconButton::IsPressed() const { return _HasFlags(STATE_FORCE_PRESSED); } // SetIcon status_t IconButton::SetIcon(const char* pathToBitmap) { status_t status = B_BAD_VALUE; if (pathToBitmap) { BBitmap* fileBitmap = NULL; // try to load bitmap from either relative or absolute path BEntry entry(pathToBitmap, true); if (!entry.Exists()) { app_info info; status = be_app->GetAppInfo(&info); if (status == B_OK) { BEntry app_entry(&info.ref, true); BPath path; app_entry.GetPath(&path); status = path.InitCheck(); if (status == B_OK) { status = path.GetParent(&path); if (status == B_OK) { status = path.Append(pathToBitmap, true); if (status == B_OK) fileBitmap = BTranslationUtils::GetBitmap(path.Path()); else printf("IconButton::SetIcon() - path.Append() failed: %s\n", strerror(status)); } else printf("IconButton::SetIcon() - path.GetParent() failed: %s\n", strerror(status)); } else printf("IconButton::SetIcon() - path.InitCheck() failed: %s\n", strerror(status)); } else printf("IconButton::SetIcon() - be_app->GetAppInfo() failed: %s\n", strerror(status)); } else fileBitmap = BTranslationUtils::GetBitmap(pathToBitmap); if (fileBitmap) { status = _MakeBitmaps(fileBitmap); delete fileBitmap; } else status = B_ERROR; } return status; } // SetIcon status_t IconButton::SetIcon(const BBitmap* bitmap) { if (bitmap && bitmap->ColorSpace() == B_CMAP8) { status_t status = bitmap->InitCheck(); if (status >= B_OK) { if (BBitmap* rgb32Bitmap = _ConvertToRGB32(bitmap)) { status = _MakeBitmaps(rgb32Bitmap); delete rgb32Bitmap; } else status = B_NO_MEMORY; } return status; } else return _MakeBitmaps(bitmap); } // SetIcon status_t IconButton::SetIcon(const BMimeType* fileType, bool small) { status_t status = fileType ? fileType->InitCheck() : B_BAD_VALUE; if (status >= B_OK) { BBitmap* mimeBitmap = new(nothrow) BBitmap(BRect(0.0, 0.0, 15.0, 15.0), B_CMAP8); if (mimeBitmap && mimeBitmap->IsValid()) { status = fileType->GetIcon(mimeBitmap, small ? B_MINI_ICON : B_LARGE_ICON); if (status >= B_OK) { if (BBitmap* bitmap = _ConvertToRGB32(mimeBitmap)) { status = _MakeBitmaps(bitmap); delete bitmap; } else printf("IconButton::SetIcon() - B_RGB32 bitmap is not valid\n"); } else printf("IconButton::SetIcon() - fileType->GetIcon() failed: %s\n", strerror(status)); } else printf("IconButton::SetIcon() - B_CMAP8 bitmap is not valid\n"); delete mimeBitmap; } else printf("IconButton::SetIcon() - fileType is not valid: %s\n", strerror(status)); return status; } // SetIcon status_t IconButton::SetIcon(const unsigned char* bitsFromQuickRes, uint32 width, uint32 height, color_space format, bool convertToBW) { status_t status = B_BAD_VALUE; if (bitsFromQuickRes && width > 0 && height > 0) { BBitmap* quickResBitmap = new(nothrow) BBitmap(BRect(0.0, 0.0, width - 1.0, height - 1.0), format); status = quickResBitmap ? quickResBitmap->InitCheck() : B_ERROR; if (status >= B_OK) { // It doesn't look right to copy BitsLength() bytes, but bitmaps // exported from QuickRes still contain their padding, so it is alright. memcpy(quickResBitmap->Bits(), bitsFromQuickRes, quickResBitmap->BitsLength()); if (format != B_RGB32 && format != B_RGBA32 && format != B_RGB32_BIG && format != B_RGBA32_BIG) { // colorspace needs conversion BBitmap* bitmap = new(nothrow) BBitmap(quickResBitmap->Bounds(), B_RGB32, true); if (bitmap && bitmap->IsValid()) { BView* helper = new BView(bitmap->Bounds(), "helper", B_FOLLOW_NONE, B_WILL_DRAW); if (bitmap->Lock()) { bitmap->AddChild(helper); helper->SetHighColor(ui_color(B_PANEL_BACKGROUND_COLOR)); helper->FillRect(helper->Bounds()); helper->SetDrawingMode(B_OP_OVER); helper->DrawBitmap(quickResBitmap, BPoint(0.0, 0.0)); helper->Sync(); bitmap->Unlock(); } status = _MakeBitmaps(bitmap); } else printf("IconButton::SetIcon() - B_RGB32 bitmap is not valid\n"); delete bitmap; } else { // native colorspace (32 bits) if (convertToBW) { // convert to gray scale icon uint8* bits = (uint8*)quickResBitmap->Bits(); uint32 bpr = quickResBitmap->BytesPerRow(); for (uint32 y = 0; y < height; y++) { uint8* handle = bits; uint8 gray; for (uint32 x = 0; x < width; x++) { gray = uint8((116 * handle[0] + 600 * handle[1] + 308 * handle[2]) / 1024); handle[0] = gray; handle[1] = gray; handle[2] = gray; handle += 4; } bits += bpr; } } status = _MakeBitmaps(quickResBitmap); } } else printf("IconButton::SetIcon() - error allocating bitmap: %s\n", strerror(status)); delete quickResBitmap; } return status; } // ClearIcon void IconButton::ClearIcon() { _DeleteBitmaps(); _Update(); } // Bitmap BBitmap* IconButton::Bitmap() const { BBitmap* bitmap = NULL; if (fNormalBitmap && fNormalBitmap->IsValid()) { bitmap = new(nothrow) BBitmap(fNormalBitmap); if (bitmap->IsValid()) { // TODO: remove this functionality when we use real transparent bitmaps uint8* bits = (uint8*)bitmap->Bits(); uint32 bpr = bitmap->BytesPerRow(); uint32 width = bitmap->Bounds().IntegerWidth() + 1; uint32 height = bitmap->Bounds().IntegerHeight() + 1; color_space format = bitmap->ColorSpace(); if (format == B_CMAP8) { // replace gray with magic transparent index } else if (format == B_RGB32) { for (uint32 y = 0; y < height; y++) { uint8* bitsHandle = bits; for (uint32 x = 0; x < width; x++) { if (bitsHandle[0] == 216 && bitsHandle[1] == 216 && bitsHandle[2] == 216) { bitsHandle[3] = 0; // make this pixel completely transparent } bitsHandle += 4; } bits += bpr; } } } else { delete bitmap; bitmap = NULL; } } return bitmap; } // DrawBorder bool IconButton::DrawBorder() const { return (IsEnabled() && (_HasFlags(STATE_INSIDE) || _HasFlags(STATE_TRACKING)) || _HasFlags(STATE_FORCE_PRESSED)); } // DrawNormalBorder void IconButton::DrawNormalBorder(BRect r, rgb_color background, rgb_color shadow, rgb_color darkShadow, rgb_color lightShadow, rgb_color light) { _DrawFrame(r, shadow, darkShadow, light, lightShadow); } // DrawPressedBorder void IconButton::DrawPressedBorder(BRect r, rgb_color background, rgb_color shadow, rgb_color darkShadow, rgb_color lightShadow, rgb_color light) { _DrawFrame(r, shadow, light, darkShadow, background); } // IsValid bool IconButton::IsValid() const { return (fNormalBitmap && fDisabledBitmap && fClickedBitmap && fDisabledClickedBitmap && fNormalBitmap->IsValid() && fDisabledBitmap->IsValid() && fClickedBitmap->IsValid() && fDisabledClickedBitmap->IsValid()); } // Value int32 IconButton::Value() const { return _HasFlags(STATE_PRESSED) ? B_CONTROL_ON : B_CONTROL_OFF; } // SetValue void IconButton::SetValue(int32 value) { if (value) _AddFlags(STATE_PRESSED); else _ClearFlags(STATE_PRESSED); } // IsEnabled bool IconButton::IsEnabled() const { return _HasFlags(STATE_ENABLED) ? B_CONTROL_ON : B_CONTROL_OFF; } // SetEnabled void IconButton::SetEnabled(bool enabled) { if (enabled) _AddFlags(STATE_ENABLED); else _ClearFlags(STATE_ENABLED | STATE_TRACKING | STATE_INSIDE); } // _ConvertToRGB32 BBitmap* IconButton::_ConvertToRGB32(const BBitmap* bitmap) const { BBitmap* convertedBitmap = new(nothrow) BBitmap(bitmap->Bounds(), B_BITMAP_ACCEPTS_VIEWS, B_RGBA32); if (convertedBitmap && convertedBitmap->IsValid()) { memset(convertedBitmap->Bits(), 0, convertedBitmap->BitsLength()); BView* helper = new BView(bitmap->Bounds(), "helper", B_FOLLOW_NONE, B_WILL_DRAW); if (convertedBitmap->Lock()) { convertedBitmap->AddChild(helper); helper->SetDrawingMode(B_OP_OVER); helper->DrawBitmap(bitmap, BPoint(0.0, 0.0)); helper->Sync(); convertedBitmap->Unlock(); } } else { delete convertedBitmap; convertedBitmap = NULL; } return convertedBitmap; } // _MakeBitmaps status_t IconButton::_MakeBitmaps(const BBitmap* bitmap) { status_t status = bitmap ? bitmap->InitCheck() : B_BAD_VALUE; if (status >= B_OK) { // make our own versions of the bitmap BRect b(bitmap->Bounds()); _DeleteBitmaps(); color_space format = bitmap->ColorSpace(); fNormalBitmap = new(nothrow) BBitmap(b, format); fDisabledBitmap = new(nothrow) BBitmap(b, format); fClickedBitmap = new(nothrow) BBitmap(b, format); fDisabledClickedBitmap = new(nothrow) BBitmap(b, format); if (IsValid()) { // copy bitmaps from file bitmap uint8* nBits = (uint8*)fNormalBitmap->Bits(); uint8* dBits = (uint8*)fDisabledBitmap->Bits(); uint8* cBits = (uint8*)fClickedBitmap->Bits(); uint8* dcBits = (uint8*)fDisabledClickedBitmap->Bits(); uint8* fBits = (uint8*)bitmap->Bits(); int32 nbpr = fNormalBitmap->BytesPerRow(); int32 fbpr = bitmap->BytesPerRow(); int32 pixels = b.IntegerWidth() + 1; int32 lines = b.IntegerHeight() + 1; // nontransparent version: if (format == B_RGB32 || format == B_RGB32_BIG) { // iterate over color components for (int32 y = 0; y < lines; y++) { for (int32 x = 0; x < pixels; x++) { int32 nOffset = 4 * x; int32 fOffset = 4 * x; nBits[nOffset + 0] = fBits[fOffset + 0]; nBits[nOffset + 1] = fBits[fOffset + 1]; nBits[nOffset + 2] = fBits[fOffset + 2]; nBits[nOffset + 3] = 255; // clicked bits are darker (lame method...) cBits[nOffset + 0] = (uint8)((float)nBits[nOffset + 0] * 0.8); cBits[nOffset + 1] = (uint8)((float)nBits[nOffset + 1] * 0.8); cBits[nOffset + 2] = (uint8)((float)nBits[nOffset + 2] * 0.8); cBits[nOffset + 3] = 255; // disabled bits have less contrast (lame method...) uint8 grey = 216; float dist = (nBits[nOffset + 0] - grey) * 0.4; dBits[nOffset + 0] = (uint8)(grey + dist); dist = (nBits[nOffset + 1] - grey) * 0.4; dBits[nOffset + 1] = (uint8)(grey + dist); dist = (nBits[nOffset + 2] - grey) * 0.4; dBits[nOffset + 2] = (uint8)(grey + dist); dBits[nOffset + 3] = 255; // disabled bits have less contrast (lame method...) grey = 188; dist = (nBits[nOffset + 0] - grey) * 0.4; dcBits[nOffset + 0] = (uint8)(grey + dist); dist = (nBits[nOffset + 1] - grey) * 0.4; dcBits[nOffset + 1] = (uint8)(grey + dist); dist = (nBits[nOffset + 2] - grey) * 0.4; dcBits[nOffset + 2] = (uint8)(grey + dist); dcBits[nOffset + 3] = 255; } nBits += nbpr; dBits += nbpr; cBits += nbpr; dcBits += nbpr; fBits += fbpr; } // transparent version: } else if (format == B_RGBA32 || format == B_RGBA32_BIG) { // iterate over color components for (int32 y = 0; y < lines; y++) { for (int32 x = 0; x < pixels; x++) { int32 nOffset = 4 * x; int32 fOffset = 4 * x; nBits[nOffset + 0] = fBits[fOffset + 0]; nBits[nOffset + 1] = fBits[fOffset + 1]; nBits[nOffset + 2] = fBits[fOffset + 2]; nBits[nOffset + 3] = fBits[fOffset + 3]; // clicked bits are darker (lame method...) cBits[nOffset + 0] = (uint8)(nBits[nOffset + 0] * 0.8); cBits[nOffset + 1] = (uint8)(nBits[nOffset + 1] * 0.8); cBits[nOffset + 2] = (uint8)(nBits[nOffset + 2] * 0.8); cBits[nOffset + 3] = fBits[fOffset + 3]; // disabled bits have less opacity dBits[nOffset + 0] = fBits[fOffset + 0]; dBits[nOffset + 1] = fBits[fOffset + 1]; dBits[nOffset + 2] = fBits[fOffset + 2]; dBits[nOffset + 3] = (uint8)(fBits[fOffset + 3] * 0.5); // disabled bits have less contrast (lame method...) dcBits[nOffset + 0] = (uint8)(nBits[nOffset + 0] * 0.8); dcBits[nOffset + 1] = (uint8)(nBits[nOffset + 1] * 0.8); dcBits[nOffset + 2] = (uint8)(nBits[nOffset + 2] * 0.8); dcBits[nOffset + 3] = (uint8)(fBits[fOffset + 3] * 0.5); } nBits += nbpr; dBits += nbpr; cBits += nbpr; dcBits += nbpr; fBits += fbpr; } // unsupported format } else { printf("IconButton::_MakeBitmaps() - bitmap has unsupported colorspace\n"); status = B_MISMATCHED_VALUES; _DeleteBitmaps(); } } else { printf("IconButton::_MakeBitmaps() - error allocating local bitmaps\n"); status = B_NO_MEMORY; _DeleteBitmaps(); } } else printf("IconButton::_MakeBitmaps() - bitmap is not valid\n"); return status; } // _DeleteBitmaps void IconButton::_DeleteBitmaps() { delete fNormalBitmap; fNormalBitmap = NULL; delete fDisabledBitmap; fDisabledBitmap = NULL; delete fClickedBitmap; fClickedBitmap = NULL; delete fDisabledClickedBitmap; fDisabledClickedBitmap = NULL; } // _Update void IconButton::_Update() { if (LockLooper()) { Invalidate(); UnlockLooper(); } } // _AddFlags void IconButton::_AddFlags(uint32 flags) { if (!(fButtonState & flags)) { fButtonState |= flags; _Update(); } } // _ClearFlags void IconButton::_ClearFlags(uint32 flags) { if (fButtonState & flags) { fButtonState &= ~flags; _Update(); } } // _HasFlags bool IconButton::_HasFlags(uint32 flags) const { return (fButtonState & flags); } // _DrawFrame void IconButton::_DrawFrame(BRect r, rgb_color col1, rgb_color col2, rgb_color col3, rgb_color col4) { BeginLineArray(8); AddLine(BPoint(r.left, r.bottom), BPoint(r.left, r.top), col1); AddLine(BPoint(r.left + 1.0, r.top), BPoint(r.right, r.top), col1); AddLine(BPoint(r.right, r.top + 1.0), BPoint(r.right, r.bottom), col2); AddLine(BPoint(r.right - 1.0, r.bottom), BPoint(r.left + 1.0, r.bottom), col2); r.InsetBy(1.0, 1.0); AddLine(BPoint(r.left, r.bottom), BPoint(r.left, r.top), col3); AddLine(BPoint(r.left + 1.0, r.top), BPoint(r.right, r.top), col3); AddLine(BPoint(r.right, r.top + 1.0), BPoint(r.right, r.bottom), col4); AddLine(BPoint(r.right - 1.0, r.bottom), BPoint(r.left + 1.0, r.bottom), col4); EndLineArray(); }