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