1/*
2 * Copyright 2001-2020 Haiku, Inc.
3 * Distributed under the terms of the MIT License.
4 *
5 * Authors:
6 *		Stephan A��mus, superstippi@gmx.de
7 *		DarkWyrm, bpmagic@columbus.rr.com
8 *		Ryan Leavengood, leavengood@gmail.com
9 *		Philippe Saint-Pierre, stpere@gmail.com
10 *		John Scipione, jscipione@gmail.com
11 *		Ingo Weinhold, ingo_weinhold@gmx.de
12 *		Clemens Zeidler, haiku@clemens-zeidler.de
13 *		Joseph Groover, looncraz@looncraz.net
14 *		Tri-Edge AI
15 *		Jacob Secunda, secundja@gmail.com
16 */
17
18
19/*!	Default and fallback decorator for the app_server - the yellow tabs */
20
21
22#include "DefaultDecorator.h"
23
24#include <algorithm>
25#include <cmath>
26#include <new>
27#include <stdio.h>
28
29#include <Autolock.h>
30#include <Debug.h>
31#include <GradientLinear.h>
32#include <Rect.h>
33#include <Region.h>
34#include <View.h>
35
36#include <WindowPrivate.h>
37
38#include "BitmapDrawingEngine.h"
39#include "DesktopSettings.h"
40#include "DrawingEngine.h"
41#include "DrawState.h"
42#include "FontManager.h"
43#include "PatternHandler.h"
44#include "ServerBitmap.h"
45
46
47//#define DEBUG_DECORATOR
48#ifdef DEBUG_DECORATOR
49#	define STRACE(x) printf x
50#else
51#	define STRACE(x) ;
52#endif
53
54
55static inline uint8
56blend_color_value(uint8 a, uint8 b, float position)
57{
58	int16 delta = (int16)b - a;
59	int32 value = a + (int32)(position * delta);
60	if (value > 255)
61		return 255;
62	if (value < 0)
63		return 0;
64
65	return (uint8)value;
66}
67
68
69//	#pragma mark -
70
71
72// TODO: get rid of DesktopSettings here, and introduce private accessor
73//	methods to the Decorator base class
74DefaultDecorator::DefaultDecorator(DesktopSettings& settings, BRect rect,
75	Desktop* desktop)
76	:
77	TabDecorator(settings, rect, desktop)
78{
79	// TODO: If the decorator was created with a frame too small, it should
80	// resize itself!
81
82	STRACE(("DefaultDecorator:\n"));
83	STRACE(("\tFrame (%.1f,%.1f,%.1f,%.1f)\n",
84		rect.left, rect.top, rect.right, rect.bottom));
85}
86
87
88DefaultDecorator::~DefaultDecorator()
89{
90	STRACE(("DefaultDecorator: ~DefaultDecorator()\n"));
91}
92
93
94// #pragma mark - Public methods
95
96
97/*!	Returns the frame colors for the specified decorator component.
98
99	The meaning of the color array elements depends on the specified component.
100	For some components some array elements are unused.
101
102	\param component The component for which to return the frame colors.
103	\param highlight The highlight set for the component.
104	\param colors An array of colors to be initialized by the function.
105*/
106void
107DefaultDecorator::GetComponentColors(Component component, uint8 highlight,
108	ComponentColors _colors, Decorator::Tab* _tab)
109{
110	Decorator::Tab* tab = static_cast<Decorator::Tab*>(_tab);
111	switch (component) {
112		case COMPONENT_TAB:
113			if (tab && tab->buttonFocus) {
114				_colors[COLOR_TAB_FRAME_LIGHT]
115					= tint_color(fFocusFrameColor, B_DARKEN_2_TINT);
116				_colors[COLOR_TAB_FRAME_DARK]
117					= tint_color(fFocusFrameColor, B_DARKEN_3_TINT);
118				_colors[COLOR_TAB] = fFocusTabColor;
119				_colors[COLOR_TAB_LIGHT] = fFocusTabColorLight;
120				_colors[COLOR_TAB_BEVEL] = fFocusTabColorBevel;
121				_colors[COLOR_TAB_SHADOW] = fFocusTabColorShadow;
122				_colors[COLOR_TAB_TEXT] = fFocusTextColor;
123			} else {
124				_colors[COLOR_TAB_FRAME_LIGHT]
125					= tint_color(fNonFocusFrameColor, B_DARKEN_2_TINT);
126				_colors[COLOR_TAB_FRAME_DARK]
127					= tint_color(fNonFocusFrameColor, B_DARKEN_3_TINT);
128				_colors[COLOR_TAB] = fNonFocusTabColor;
129				_colors[COLOR_TAB_LIGHT] = fNonFocusTabColorLight;
130				_colors[COLOR_TAB_BEVEL] = fNonFocusTabColorBevel;
131				_colors[COLOR_TAB_SHADOW] = fNonFocusTabColorShadow;
132				_colors[COLOR_TAB_TEXT] = fNonFocusTextColor;
133			}
134			break;
135
136		case COMPONENT_CLOSE_BUTTON:
137		case COMPONENT_ZOOM_BUTTON:
138			if (tab && tab->buttonFocus) {
139				_colors[COLOR_BUTTON] = fFocusTabColor;
140				_colors[COLOR_BUTTON_LIGHT] = fFocusTabColorLight;
141			} else {
142				_colors[COLOR_BUTTON] = fNonFocusTabColor;
143				_colors[COLOR_BUTTON_LIGHT] = fNonFocusTabColorLight;
144			}
145			break;
146
147		case COMPONENT_LEFT_BORDER:
148		case COMPONENT_RIGHT_BORDER:
149		case COMPONENT_TOP_BORDER:
150		case COMPONENT_BOTTOM_BORDER:
151		case COMPONENT_RESIZE_CORNER:
152		default:
153			if (tab && tab->buttonFocus) {
154				_colors[0] = tint_color(fFocusFrameColor, B_DARKEN_2_TINT);
155				_colors[1] = tint_color(fFocusFrameColor, B_LIGHTEN_2_TINT);
156				_colors[2] = fFocusFrameColor;
157				_colors[3] = tint_color(fFocusFrameColor,
158					(B_DARKEN_1_TINT + B_NO_TINT) / 2);
159				_colors[4] = tint_color(fFocusFrameColor, B_DARKEN_2_TINT);
160				_colors[5] = tint_color(fFocusFrameColor, B_DARKEN_3_TINT);
161			} else {
162				_colors[0] = tint_color(fNonFocusFrameColor, B_DARKEN_2_TINT);
163				_colors[1] = tint_color(fNonFocusFrameColor, B_LIGHTEN_2_TINT);
164				_colors[2] = fNonFocusFrameColor;
165				_colors[3] = tint_color(fNonFocusFrameColor,
166					(B_DARKEN_1_TINT + B_NO_TINT) / 2);
167				_colors[4] = tint_color(fNonFocusFrameColor, B_DARKEN_2_TINT);
168				_colors[5] = tint_color(fNonFocusFrameColor, B_DARKEN_3_TINT);
169			}
170
171			// for the resize-border highlight dye everything bluish.
172			if (highlight == HIGHLIGHT_RESIZE_BORDER) {
173				for (int32 i = 0; i < 6; i++) {
174					_colors[i].red = std::max((int)_colors[i].red - 80, 0);
175					_colors[i].green = std::max((int)_colors[i].green - 80, 0);
176					_colors[i].blue = 255;
177				}
178			}
179			break;
180	}
181}
182
183
184void
185DefaultDecorator::UpdateColors(DesktopSettings& settings)
186{
187	TabDecorator::UpdateColors(settings);
188}
189
190
191// #pragma mark - Protected methods
192
193
194void
195DefaultDecorator::_DrawFrame(BRect rect)
196{
197	STRACE(("_DrawFrame(%f,%f,%f,%f)\n", rect.left, rect.top,
198		rect.right, rect.bottom));
199
200	// NOTE: the DrawingEngine needs to be locked for the entire
201	// time for the clipping to stay valid for this decorator
202
203	if (fTopTab->look == B_NO_BORDER_WINDOW_LOOK)
204		return;
205
206	if (fBorderWidth <= 0)
207		return;
208
209	// TODO: While this works, it does not look so crisp at higher resolutions.
210#define COLORS_INDEX(i, borderWidth, nominalLimit) int32((float(i) / float(borderWidth)) * nominalLimit)
211
212	// Draw the border frame
213	BRect border = BRect(fTopBorder.LeftTop(), fBottomBorder.RightBottom());
214	switch ((int)fTopTab->look) {
215		case B_TITLED_WINDOW_LOOK:
216		case B_DOCUMENT_WINDOW_LOOK:
217		case B_MODAL_WINDOW_LOOK:
218		{
219			// top
220			if (rect.Intersects(fTopBorder)) {
221				ComponentColors colors;
222				_GetComponentColors(COMPONENT_TOP_BORDER, colors, fTopTab);
223
224				for (int8 i = 0; i < fBorderWidth; i++) {
225					const int8 colorsIndex = COLORS_INDEX(i, fBorderWidth, 5);
226					fDrawingEngine->StrokeLine(
227						BPoint(border.left + i, border.top + i),
228						BPoint(border.right - i, border.top + i),
229						colors[colorsIndex]);
230				}
231				if (fTitleBarRect.IsValid()) {
232					// grey along the bottom of the tab
233					// (overwrites "white" from frame)
234					const int overdraw = (int)ceilf(fBorderWidth / 5.0f);
235					for (int i = 1; i <= overdraw; i++) {
236						fDrawingEngine->StrokeLine(
237							BPoint(fTitleBarRect.left + 2, fTitleBarRect.bottom + i),
238							BPoint(fTitleBarRect.right - 2, fTitleBarRect.bottom + i),
239							colors[2]);
240					}
241				}
242			}
243			// left
244			if (rect.Intersects(fLeftBorder.InsetByCopy(0, -fBorderWidth))) {
245				ComponentColors colors;
246				_GetComponentColors(COMPONENT_LEFT_BORDER, colors, fTopTab);
247
248				for (int8 i = 0; i < fBorderWidth; i++) {
249					const int8 colorsIndex = COLORS_INDEX(i, fBorderWidth, 5);
250					fDrawingEngine->StrokeLine(
251						BPoint(border.left + i, border.top + i),
252						BPoint(border.left + i, border.bottom - i),
253						colors[colorsIndex]);
254				}
255			}
256			// bottom
257			if (rect.Intersects(fBottomBorder)) {
258				ComponentColors colors;
259				_GetComponentColors(COMPONENT_BOTTOM_BORDER, colors, fTopTab);
260
261				for (int8 i = 0; i < fBorderWidth; i++) {
262					const int8 colorsIndex = COLORS_INDEX(i, fBorderWidth, 5);
263					fDrawingEngine->StrokeLine(
264						BPoint(border.left + i, border.bottom - i),
265						BPoint(border.right - i, border.bottom - i),
266						colors[(4 - colorsIndex) == 4 ? 5 : (4 - colorsIndex)]);
267				}
268			}
269			// right
270			if (rect.Intersects(fRightBorder.InsetByCopy(0, -fBorderWidth))) {
271				ComponentColors colors;
272				_GetComponentColors(COMPONENT_RIGHT_BORDER, colors, fTopTab);
273
274				for (int8 i = 0; i < fBorderWidth; i++) {
275					const int8 colorsIndex = COLORS_INDEX(i, fBorderWidth, 5);
276					fDrawingEngine->StrokeLine(
277						BPoint(border.right - i, border.top + i),
278						BPoint(border.right - i, border.bottom - i),
279						colors[(4 - colorsIndex) == 4 ? 5 : (4 - colorsIndex)]);
280				}
281			}
282			break;
283		}
284
285		case B_FLOATING_WINDOW_LOOK:
286		case kLeftTitledWindowLook:
287		{
288			// top
289			if (rect.Intersects(fTopBorder)) {
290				ComponentColors colors;
291				_GetComponentColors(COMPONENT_TOP_BORDER, colors, fTopTab);
292
293				for (int8 i = 0; i < fBorderWidth; i++) {
294					const int8 colorsIndex = COLORS_INDEX(i, fBorderWidth, 3);
295					fDrawingEngine->StrokeLine(
296						BPoint(border.left + i, border.top + i),
297						BPoint(border.right - i, border.top + i),
298						colors[colorsIndex * 2]);
299				}
300				if (fTitleBarRect.IsValid() && fTopTab->look != kLeftTitledWindowLook) {
301					// grey along the bottom of the tab
302					// (overwrites "white" from frame)
303					const int overdraw = (int)ceilf(fBorderWidth / 5.0f);
304					for (int i = 1; i <= overdraw; i++) {
305						fDrawingEngine->StrokeLine(
306							BPoint(fTitleBarRect.left + 2, fTitleBarRect.bottom + i),
307							BPoint(fTitleBarRect.right - 2, fTitleBarRect.bottom + i),
308							colors[2]);
309					}
310				}
311			}
312			// left
313			if (rect.Intersects(fLeftBorder.InsetByCopy(0, -fBorderWidth))) {
314				ComponentColors colors;
315				_GetComponentColors(COMPONENT_LEFT_BORDER, colors, fTopTab);
316
317				for (int8 i = 0; i < fBorderWidth; i++) {
318					const int8 colorsIndex = COLORS_INDEX(i, fBorderWidth, 3);
319					fDrawingEngine->StrokeLine(
320						BPoint(border.left + i, border.top + i),
321						BPoint(border.left + i, border.bottom - i),
322						colors[colorsIndex * 2]);
323				}
324				if (fTopTab->look == kLeftTitledWindowLook
325					&& fTitleBarRect.IsValid()) {
326					// grey along the right side of the tab
327					// (overwrites "white" from frame)
328					fDrawingEngine->StrokeLine(
329						BPoint(fTitleBarRect.right + 1,
330							fTitleBarRect.top + 2),
331						BPoint(fTitleBarRect.right + 1,
332							fTitleBarRect.bottom - 2), colors[2]);
333				}
334			}
335			// bottom
336			if (rect.Intersects(fBottomBorder)) {
337				ComponentColors colors;
338				_GetComponentColors(COMPONENT_BOTTOM_BORDER, colors, fTopTab);
339
340				for (int8 i = 0; i < fBorderWidth; i++) {
341					const int8 colorsIndex = COLORS_INDEX(i, fBorderWidth, 3);
342					fDrawingEngine->StrokeLine(
343						BPoint(border.left + i, border.bottom - i),
344						BPoint(border.right - i, border.bottom - i),
345						colors[(2 - colorsIndex) == 2 ? 5 : (2 - colorsIndex) * 2]);
346				}
347			}
348			// right
349			if (rect.Intersects(fRightBorder.InsetByCopy(0, -fBorderWidth))) {
350				ComponentColors colors;
351				_GetComponentColors(COMPONENT_RIGHT_BORDER, colors, fTopTab);
352
353				for (int8 i = 0; i < fBorderWidth; i++) {
354					const int8 colorsIndex = COLORS_INDEX(i, fBorderWidth, 3);
355					fDrawingEngine->StrokeLine(
356						BPoint(border.right - i, border.top + i),
357						BPoint(border.right - i, border.bottom - i),
358						colors[(2 - colorsIndex) == 2 ? 5 : (2 - colorsIndex) * 2]);
359				}
360			}
361			break;
362		}
363
364		case B_BORDERED_WINDOW_LOOK:
365		{
366			// TODO: Draw the borders individually!
367			ComponentColors colors;
368			_GetComponentColors(COMPONENT_LEFT_BORDER, colors, fTopTab);
369
370			fDrawingEngine->StrokeRect(border, colors[5]);
371			break;
372		}
373
374		default:
375			// don't draw a border frame
376			break;
377	}
378
379	// Draw the resize knob if we're supposed to
380	if (!(fTopTab->flags & B_NOT_RESIZABLE)) {
381		ComponentColors colors;
382		_GetComponentColors(COMPONENT_RESIZE_CORNER, colors, fTopTab);
383
384		switch ((int)fTopTab->look) {
385			case B_DOCUMENT_WINDOW_LOOK:
386			{
387				if (fOutlinesDelta.x != 0 || fOutlinesDelta.y != 0) {
388					border.Set(fFrame.right - 13, fFrame.bottom - 13,
389						fFrame.right + 3, fFrame.bottom + 3);
390
391					if (rect.Intersects(border))
392						_DrawResizeKnob(border, false, colors);
393				}
394
395				if (rect.Intersects(fResizeRect)) {
396					_DrawResizeKnob(fResizeRect, fTopTab && IsFocus(fTopTab),
397						colors);
398				}
399
400				break;
401			}
402
403			case B_TITLED_WINDOW_LOOK:
404			case B_FLOATING_WINDOW_LOOK:
405			case B_MODAL_WINDOW_LOOK:
406			case kLeftTitledWindowLook:
407			{
408				if (!rect.Intersects(BRect(
409						fRightBorder.right - fBorderResizeLength,
410						fBottomBorder.bottom - fBorderResizeLength,
411						fRightBorder.right - 1,
412						fBottomBorder.bottom - 1)))
413					break;
414
415				fDrawingEngine->StrokeLine(
416					BPoint(fRightBorder.left,
417						fBottomBorder.bottom - fBorderResizeLength),
418					BPoint(fRightBorder.right - 1,
419						fBottomBorder.bottom - fBorderResizeLength),
420					colors[0]);
421				fDrawingEngine->StrokeLine(
422					BPoint(fRightBorder.right - fBorderResizeLength,
423						fBottomBorder.top),
424					BPoint(fRightBorder.right - fBorderResizeLength,
425						fBottomBorder.bottom - 1),
426					colors[0]);
427				break;
428			}
429
430			default:
431				// don't draw resize corner
432				break;
433		}
434	}
435}
436
437
438void
439DefaultDecorator::_DrawResizeKnob(BRect rect, bool full,
440	const ComponentColors& colors)
441{
442	float x = rect.right -= 3;
443	float y = rect.bottom -= 3;
444
445	BGradientLinear gradient;
446	gradient.SetStart(rect.LeftTop());
447	gradient.SetEnd(rect.RightBottom());
448	gradient.AddColor(colors[1], 0);
449	gradient.AddColor(colors[2], 255);
450
451	fDrawingEngine->FillRect(rect, gradient);
452
453	BPoint offset1(rect.Width(), rect.Height()),
454		offset2(rect.Width() - 1, rect.Height() - 1);
455	fDrawingEngine->StrokeLine(BPoint(x, y) - offset1,
456		BPoint(x - offset1.x, y - 2), colors[0]);
457	fDrawingEngine->StrokeLine(BPoint(x, y) - offset2,
458		BPoint(x - offset2.x, y - 1), colors[1]);
459	fDrawingEngine->StrokeLine(BPoint(x, y) - offset1,
460		BPoint(x - 2, y - offset1.y), colors[0]);
461	fDrawingEngine->StrokeLine(BPoint(x, y) - offset2,
462		BPoint(x - 1, y - offset2.y), colors[1]);
463
464	if (!full)
465		return;
466
467	static const rgb_color kWhite
468		= (rgb_color){ 255, 255, 255, 255 };
469	for (int8 i = 1; i <= 4; i++) {
470		for (int8 j = 1; j <= i; j++) {
471			BPoint pt1(x - (3 * j) + 1, y - (3 * (5 - i)) + 1);
472			BPoint pt2(x - (3 * j) + 2, y - (3 * (5 - i)) + 2);
473			fDrawingEngine->StrokePoint(pt1, colors[0]);
474			fDrawingEngine->StrokePoint(pt2, kWhite);
475		}
476	}
477}
478
479
480/*!	\brief Actually draws the tab
481
482	This function is called when the tab itself needs drawn. Other items,
483	like the window title or buttons, should not be drawn here.
484
485	\param tab The \a tab to update.
486	\param rect The area of the \a tab to update.
487*/
488void
489DefaultDecorator::_DrawTab(Decorator::Tab* tab, BRect invalid)
490{
491	STRACE(("_DrawTab(%.1f,%.1f,%.1f,%.1f)\n",
492		invalid.left, invalid.top, invalid.right, invalid.bottom));
493	const BRect& tabRect = tab->tabRect;
494	// If a window has a tab, this will draw it and any buttons which are
495	// in it.
496	if (!tabRect.IsValid() || !invalid.Intersects(tabRect))
497		return;
498
499	ComponentColors colors;
500	_GetComponentColors(COMPONENT_TAB, colors, tab);
501
502	// outer frame
503	fDrawingEngine->StrokeLine(tabRect.LeftTop(), tabRect.LeftBottom(),
504		colors[COLOR_TAB_FRAME_LIGHT]);
505	fDrawingEngine->StrokeLine(tabRect.LeftTop(), tabRect.RightTop(),
506		colors[COLOR_TAB_FRAME_LIGHT]);
507	if (tab->look != kLeftTitledWindowLook) {
508		fDrawingEngine->StrokeLine(tabRect.RightTop(), tabRect.RightBottom(),
509			colors[COLOR_TAB_FRAME_DARK]);
510	} else {
511		fDrawingEngine->StrokeLine(tabRect.LeftBottom(),
512			tabRect.RightBottom(), colors[COLOR_TAB_FRAME_DARK]);
513	}
514
515	float tabBotton = tabRect.bottom;
516	if (fTopTab != tab)
517		tabBotton -= 1;
518
519	// bevel
520	fDrawingEngine->StrokeLine(BPoint(tabRect.left + 1, tabRect.top + 1),
521		BPoint(tabRect.left + 1,
522			tabBotton - (tab->look == kLeftTitledWindowLook ? 1 : 0)),
523		colors[COLOR_TAB_BEVEL]);
524	fDrawingEngine->StrokeLine(BPoint(tabRect.left + 1, tabRect.top + 1),
525		BPoint(tabRect.right - (tab->look == kLeftTitledWindowLook ? 0 : 1),
526			tabRect.top + 1),
527		colors[COLOR_TAB_BEVEL]);
528
529	if (tab->look != kLeftTitledWindowLook) {
530		fDrawingEngine->StrokeLine(BPoint(tabRect.right - 1, tabRect.top + 2),
531			BPoint(tabRect.right - 1, tabBotton),
532			colors[COLOR_TAB_SHADOW]);
533	} else {
534		fDrawingEngine->StrokeLine(
535			BPoint(tabRect.left + 2, tabRect.bottom - 1),
536			BPoint(tabRect.right, tabRect.bottom - 1),
537			colors[COLOR_TAB_SHADOW]);
538	}
539
540	// fill
541	BGradientLinear gradient;
542	gradient.SetStart(tabRect.LeftTop());
543	gradient.AddColor(colors[COLOR_TAB_LIGHT], 0);
544	gradient.AddColor(colors[COLOR_TAB], 255);
545
546	if (tab->look != kLeftTitledWindowLook) {
547		gradient.SetEnd(tabRect.LeftBottom());
548		fDrawingEngine->FillRect(BRect(tabRect.left + 2, tabRect.top + 2,
549			tabRect.right - 2, tabBotton), gradient);
550	} else {
551		gradient.SetEnd(tabRect.RightTop());
552		fDrawingEngine->FillRect(BRect(tabRect.left + 2, tabRect.top + 2,
553			tabRect.right, tabRect.bottom - 2), gradient);
554	}
555
556	_DrawTitle(tab, tabRect);
557
558	_DrawButtons(tab, invalid);
559}
560
561
562/*!	\brief Actually draws the title
563
564	The main tasks for this function are to ensure that the decorator draws
565	the title only in its own area and drawing the title itself.
566	Using B_OP_COPY for drawing the title is recommended because of the marked
567	performance hit of the other drawing modes, but it is not a requirement.
568
569	\param tab The \a tab to update.
570	\param rect area of the title to update.
571*/
572void
573DefaultDecorator::_DrawTitle(Decorator::Tab* _tab, BRect rect)
574{
575	STRACE(("_DrawTitle(%f,%f,%f,%f)\n", rect.left, rect.top, rect.right,
576		rect.bottom));
577
578	Decorator::Tab* tab = static_cast<Decorator::Tab*>(_tab);
579
580	const BRect& tabRect = tab->tabRect;
581	const BRect& closeRect = tab->closeRect;
582	const BRect& zoomRect = tab->zoomRect;
583
584	ComponentColors colors;
585	_GetComponentColors(COMPONENT_TAB, colors, tab);
586
587	fDrawingEngine->SetDrawingMode(B_OP_OVER);
588	fDrawingEngine->SetHighColor(colors[COLOR_TAB_TEXT]);
589	fDrawingEngine->SetFont(fDrawState.Font());
590
591	// figure out position of text
592	font_height fontHeight;
593	fDrawState.Font().GetHeight(fontHeight);
594
595	BPoint titlePos;
596	if (tab->look != kLeftTitledWindowLook) {
597		titlePos.x = closeRect.IsValid() ? closeRect.right + tab->textOffset
598			: tabRect.left + tab->textOffset;
599		titlePos.y = floorf(((tabRect.top + 2.0) + tabRect.bottom
600			+ fontHeight.ascent + fontHeight.descent) / 2.0
601			- fontHeight.descent + 0.5);
602	} else {
603		titlePos.x = floorf(((tabRect.left + 2.0) + tabRect.right
604			+ fontHeight.ascent + fontHeight.descent) / 2.0
605			- fontHeight.descent + 0.5);
606		titlePos.y = zoomRect.IsValid() ? zoomRect.top - tab->textOffset
607			: tabRect.bottom - tab->textOffset;
608	}
609
610	fDrawingEngine->DrawString(tab->truncatedTitle, tab->truncatedTitleLength,
611		titlePos);
612
613	fDrawingEngine->SetDrawingMode(B_OP_COPY);
614}
615
616
617/*!	\brief Actually draws the close button
618
619	Unless a subclass has a particularly large button, it is probably
620	unnecessary to check the update rectangle.
621
622	\param _tab The \a tab to update.
623	\param direct Draw without double buffering.
624	\param rect The area of the button to update.
625*/
626void
627DefaultDecorator::_DrawClose(Decorator::Tab* _tab, bool direct, BRect rect)
628{
629	STRACE(("_DrawClose(%f,%f,%f,%f)\n", rect.left, rect.top, rect.right,
630		rect.bottom));
631
632	Decorator::Tab* tab = static_cast<Decorator::Tab*>(_tab);
633
634	int32 index = (tab->buttonFocus ? 0 : 1) + (tab->closePressed ? 0 : 2);
635	ServerBitmap* bitmap = tab->closeBitmaps[index];
636	if (bitmap == NULL) {
637		bitmap = _GetBitmapForButton(tab, COMPONENT_CLOSE_BUTTON,
638			tab->closePressed, rect.IntegerWidth(), rect.IntegerHeight());
639		tab->closeBitmaps[index] = bitmap;
640	}
641
642	_DrawButtonBitmap(bitmap, direct, rect);
643}
644
645
646/*!	\brief Actually draws the zoom button
647
648	Unless a subclass has a particularly large button, it is probably
649	unnecessary to check the update rectangle.
650
651	\param _tab The \a tab to update.
652	\param direct Draw without double buffering.
653	\param rect The area of the button to update.
654*/
655void
656DefaultDecorator::_DrawZoom(Decorator::Tab* _tab, bool direct, BRect rect)
657{
658	STRACE(("_DrawZoom(%f,%f,%f,%f)\n", rect.left, rect.top, rect.right,
659		rect.bottom));
660
661	if (rect.IntegerWidth() < 1)
662		return;
663
664	Decorator::Tab* tab = static_cast<Decorator::Tab*>(_tab);
665	int32 index = (tab->buttonFocus ? 0 : 1) + (tab->zoomPressed ? 0 : 2);
666	ServerBitmap* bitmap = tab->zoomBitmaps[index];
667	if (bitmap == NULL) {
668		bitmap = _GetBitmapForButton(tab, COMPONENT_ZOOM_BUTTON,
669			tab->zoomPressed, rect.IntegerWidth(), rect.IntegerHeight());
670		tab->zoomBitmaps[index] = bitmap;
671	}
672
673	_DrawButtonBitmap(bitmap, direct, rect);
674}
675
676
677void
678DefaultDecorator::_DrawMinimize(Decorator::Tab* tab, bool direct, BRect rect)
679{
680	// This decorator doesn't have this button
681}
682
683
684// #pragma mark - Private methods
685
686
687void
688DefaultDecorator::_DrawButtonBitmap(ServerBitmap* bitmap, bool direct,
689	BRect rect)
690{
691	if (bitmap == NULL)
692		return;
693
694	bool copyToFrontEnabled = fDrawingEngine->CopyToFrontEnabled();
695	fDrawingEngine->SetCopyToFrontEnabled(direct);
696	drawing_mode oldMode;
697	fDrawingEngine->SetDrawingMode(B_OP_OVER, oldMode);
698	fDrawingEngine->DrawBitmap(bitmap, rect.OffsetToCopy(0, 0), rect);
699	fDrawingEngine->SetDrawingMode(oldMode);
700	fDrawingEngine->SetCopyToFrontEnabled(copyToFrontEnabled);
701}
702
703
704/*!	\brief Draws a framed rectangle with a gradient.
705	\param engine The drawing engine to use.
706	\param rect The rectangular area to draw in.
707	\param down The rectangle should be drawn recessed or not.
708	\param colors A button color array of the colors to be used.
709*/
710void
711DefaultDecorator::_DrawBlendedRect(DrawingEngine* engine, const BRect rect,
712	bool down, const ComponentColors& colors)
713{
714	// figure out which colors to use
715	rgb_color startColor, endColor;
716	if (down) {
717		startColor = tint_color(colors[COLOR_BUTTON], B_DARKEN_1_TINT);
718		endColor = colors[COLOR_BUTTON_LIGHT];
719	} else {
720		startColor = tint_color(colors[COLOR_BUTTON], B_LIGHTEN_MAX_TINT);
721		endColor = colors[COLOR_BUTTON];
722	}
723
724	// fill
725	BRect fillRect(rect.InsetByCopy(1.0f, 1.0f));
726
727	BGradientLinear gradient;
728	gradient.SetStart(fillRect.LeftTop());
729	gradient.SetEnd(fillRect.RightBottom());
730	gradient.AddColor(startColor, 0);
731	gradient.AddColor(endColor, 255);
732
733	engine->FillRect(fillRect, gradient);
734
735	// outline
736	engine->StrokeRect(rect, tint_color(colors[COLOR_BUTTON], B_DARKEN_2_TINT));
737}
738
739
740ServerBitmap*
741DefaultDecorator::_GetBitmapForButton(Decorator::Tab* tab, Component item,
742	bool down, int32 width, int32 height)
743{
744	// TODO: the list of shared bitmaps is never freed
745	struct decorator_bitmap {
746		Component			item;
747		bool				down;
748		int32				width;
749		int32				height;
750		rgb_color			baseColor;
751		rgb_color			lightColor;
752		UtilityBitmap*		bitmap;
753		decorator_bitmap*	next;
754	};
755
756	static BLocker sBitmapListLock("decorator lock", true);
757	static decorator_bitmap* sBitmapList = NULL;
758
759	ComponentColors colors;
760	_GetComponentColors(item, colors, tab);
761
762	BAutolock locker(sBitmapListLock);
763
764	// search our list for a matching bitmap
765	// TODO: use a hash map instead?
766	decorator_bitmap* current = sBitmapList;
767	while (current) {
768		if (current->item == item && current->down == down
769			&& current->width == width && current->height == height
770			&& current->baseColor == colors[COLOR_BUTTON]
771			&& current->lightColor == colors[COLOR_BUTTON_LIGHT]) {
772			return current->bitmap;
773		}
774
775		current = current->next;
776	}
777
778	static BitmapDrawingEngine* sBitmapDrawingEngine = NULL;
779
780	// didn't find any bitmap, create a new one
781	if (sBitmapDrawingEngine == NULL)
782		sBitmapDrawingEngine = new(std::nothrow) BitmapDrawingEngine();
783	if (sBitmapDrawingEngine == NULL
784		|| sBitmapDrawingEngine->SetSize(width, height) != B_OK)
785		return NULL;
786
787	BRect rect(0, 0, width - 1, height - 1);
788
789	STRACE(("DefaultDecorator creating bitmap for %s %s at size %ldx%ld\n",
790		item == COMPONENT_CLOSE_BUTTON ? "close" : "zoom",
791		down ? "down" : "up", width, height));
792	switch (item) {
793		case COMPONENT_CLOSE_BUTTON:
794			_DrawBlendedRect(sBitmapDrawingEngine, rect, down, colors);
795			break;
796
797		case COMPONENT_ZOOM_BUTTON:
798		{
799			sBitmapDrawingEngine->FillRect(rect, B_TRANSPARENT_COLOR);
800				// init the background
801
802			float inset = floorf(width / 4.0);
803			BRect zoomRect(rect);
804			zoomRect.left += inset;
805			zoomRect.top += inset;
806			_DrawBlendedRect(sBitmapDrawingEngine, zoomRect, down, colors);
807
808			inset = floorf(width / 2.1);
809			zoomRect = rect;
810			zoomRect.right -= inset;
811			zoomRect.bottom -= inset;
812			_DrawBlendedRect(sBitmapDrawingEngine, zoomRect, down, colors);
813			break;
814		}
815
816		default:
817			break;
818	}
819
820	UtilityBitmap* bitmap = sBitmapDrawingEngine->ExportToBitmap(width, height,
821		B_RGB32);
822	if (bitmap == NULL)
823		return NULL;
824
825	// bitmap ready, put it into the list
826	decorator_bitmap* entry = new(std::nothrow) decorator_bitmap;
827	if (entry == NULL) {
828		delete bitmap;
829		return NULL;
830	}
831
832	entry->item = item;
833	entry->down = down;
834	entry->width = width;
835	entry->height = height;
836	entry->bitmap = bitmap;
837	entry->baseColor = colors[COLOR_BUTTON];
838	entry->lightColor = colors[COLOR_BUTTON_LIGHT];
839	entry->next = sBitmapList;
840	sBitmapList = entry;
841
842	return bitmap;
843}
844
845
846void
847DefaultDecorator::_GetComponentColors(Component component,
848	ComponentColors _colors, Decorator::Tab* tab)
849{
850	// get the highlight for our component
851	Region region = REGION_NONE;
852	switch (component) {
853		case COMPONENT_TAB:
854			region = REGION_TAB;
855			break;
856		case COMPONENT_CLOSE_BUTTON:
857			region = REGION_CLOSE_BUTTON;
858			break;
859		case COMPONENT_ZOOM_BUTTON:
860			region = REGION_ZOOM_BUTTON;
861			break;
862		case COMPONENT_LEFT_BORDER:
863			region = REGION_LEFT_BORDER;
864			break;
865		case COMPONENT_RIGHT_BORDER:
866			region = REGION_RIGHT_BORDER;
867			break;
868		case COMPONENT_TOP_BORDER:
869			region = REGION_TOP_BORDER;
870			break;
871		case COMPONENT_BOTTOM_BORDER:
872			region = REGION_BOTTOM_BORDER;
873			break;
874		case COMPONENT_RESIZE_CORNER:
875			region = REGION_RIGHT_BOTTOM_CORNER;
876			break;
877	}
878
879	return GetComponentColors(component, RegionHighlight(region), _colors, tab);
880}
881