1/*
2 * Copyright 2009-2014 Haiku, Inc. All rights reserved.
3 * Distributed under the terms of the MIT License.
4 *
5 * Authors:
6 *		DarkWyrm, bpmagic@columbus.rr.com
7 *		Adrien Destugues, pulkomandy@gmail.com
8 *		John Scipione, jscipione@gmail.com
9 */
10
11
12/*! Decorator resembling Windows 95 */
13
14
15#include "WinDecorator.h"
16
17#include <new>
18#include <stdio.h>
19
20#include <Point.h>
21#include <Rect.h>
22#include <View.h>
23
24#include "DesktopSettings.h"
25#include "DrawingEngine.h"
26#include "PatternHandler.h"
27#include "RGBColor.h"
28
29
30//#define DEBUG_DECORATOR
31#ifdef DEBUG_DECORATOR
32#	define STRACE(x) printf x
33#else
34#	define STRACE(x) ;
35#endif
36
37
38WinDecorAddOn::WinDecorAddOn(image_id id, const char* name)
39	:
40	DecorAddOn(id, name)
41{
42}
43
44
45Decorator*
46WinDecorAddOn::_AllocateDecorator(DesktopSettings& settings, BRect rect,
47	Desktop* desktop)
48{
49	return new (std::nothrow)WinDecorator(settings, rect, desktop);
50}
51
52
53WinDecorator::WinDecorator(DesktopSettings& settings, BRect frame,
54	Desktop* desktop)
55	:
56	SATDecorator(settings, frame, desktop),
57	// common colors to both focus and non focus state
58	fFrameHighColor((rgb_color){ 255, 255, 255, 255 }),
59	fFrameMidColor((rgb_color){ 216, 216, 216, 255 }),
60	fFrameLowColor((rgb_color){ 110, 110, 110, 255 }),
61	fFrameLowerColor((rgb_color){ 0, 0, 0, 255 }),
62
63	// state based colors
64	fFocusTabColor(settings.UIColor(B_WINDOW_TAB_COLOR)),
65	fFocusTextColor(settings.UIColor(B_WINDOW_TEXT_COLOR)),
66	fNonFocusTabColor(settings.UIColor(B_WINDOW_INACTIVE_TAB_COLOR)),
67	fNonFocusTextColor(settings.UIColor(B_WINDOW_INACTIVE_TEXT_COLOR))
68{
69	STRACE(("WinDecorator:\n"));
70	STRACE(("\tFrame (%.1f,%.1f,%.1f,%.1f)\n",
71		frame.left, frame.top, frame.right, frame.bottom));
72}
73
74
75WinDecorator::~WinDecorator()
76{
77	STRACE(("~WinDecorator()\n"));
78}
79
80
81/*!	\brief Updates the decorator in the rectangular area \a updateRect.
82
83	Updates all areas which intersect the frame and tab.
84
85	\param updateRect The rectangular area to update.
86*/
87void
88WinDecorator::Draw(BRect updateRect)
89{
90	STRACE(("WinDecorator::Draw(BRect updateRect): "));
91	updateRect.PrintToStream();
92
93	// We need to draw a few things: the tab, the resize knob, the borders,
94	// and the buttons
95	fDrawingEngine->SetDrawState(&fDrawState);
96
97	_DrawFrame(updateRect & fBorderRect);
98	_DrawTabs(updateRect & fTitleBarRect);
99}
100
101
102//! Forces a complete decorator update
103void
104WinDecorator::Draw()
105{
106	STRACE(("WinDecorator: Draw()"));
107
108	// Easy way to draw everything - no worries about drawing only certain
109	// things
110	fDrawingEngine->SetDrawState(&fDrawState);
111
112	_DrawFrame(fBorderRect);
113	_DrawTabs(fTitleBarRect);
114}
115
116
117// TODO : add GetSizeLimits
118
119
120Decorator::Region
121WinDecorator::RegionAt(BPoint where, int32& tabIndex) const
122{
123	tabIndex = -1;
124
125	for (int32 i = 0; i < fTabList.CountItems(); i++) {
126		Decorator::Tab* tab = fTabList.ItemAt(i);
127		if (tab->minimizeRect.Contains(where)) {
128			tabIndex = i;
129			return REGION_MINIMIZE_BUTTON;
130		}
131	}
132	// Let the base class version identify hits of the buttons and the tab.
133	Region region = Decorator::RegionAt(where, tabIndex);
134	if (region != REGION_NONE)
135		return region;
136
137	// check the resize corner
138	if (fTopTab->look == B_DOCUMENT_WINDOW_LOOK && fResizeRect.Contains(where))
139		return REGION_RIGHT_BOTTOM_CORNER;
140
141	// hit-test the borders
142	if (!(fTopTab->flags & B_NOT_RESIZABLE)
143		&& (fTopTab->look == B_TITLED_WINDOW_LOOK
144			|| fTopTab->look == B_FLOATING_WINDOW_LOOK
145			|| fTopTab->look == B_MODAL_WINDOW_LOOK)
146		&& fBorderRect.Contains(where) && !fFrame.Contains(where)) {
147		return REGION_BOTTOM_BORDER;
148			// TODO Determine the actual border!
149	}
150
151	return REGION_NONE;
152}
153
154
155bool
156WinDecorator::SetRegionHighlight(Region region, uint8 highlight,
157	BRegion* dirty, int32 tabIndex)
158{
159	Decorator::Tab* tab
160		= static_cast<Decorator::Tab*>(_TabAt(tabIndex));
161	if (tab != NULL) {
162		tab->isHighlighted = highlight != 0;
163		// Invalidate the bitmap caches for the close/minimize/zoom button
164		// when the highlight changes.
165		switch (region) {
166			case REGION_CLOSE_BUTTON:
167				if (highlight != RegionHighlight(region))
168					memset(&tab->closeBitmaps, 0, sizeof(tab->closeBitmaps));
169				break;
170			case REGION_MINIMIZE_BUTTON:
171				if (highlight != RegionHighlight(region)) {
172					memset(&tab->minimizeBitmaps, 0,
173						sizeof(tab->minimizeBitmaps));
174				}
175				break;
176			case REGION_ZOOM_BUTTON:
177				if (highlight != RegionHighlight(region))
178					memset(&tab->zoomBitmaps, 0, sizeof(tab->zoomBitmaps));
179				break;
180			default:
181				break;
182		}
183	}
184
185	return Decorator::SetRegionHighlight(region, highlight, dirty, tabIndex);
186}
187
188
189void
190WinDecorator::_DoLayout()
191{
192	STRACE(("WinDecorator()::_DoLayout()\n"));
193
194	bool hasTab = false;
195
196	fBorderRect = fFrame;
197	switch ((int)fTopTab->look) {
198		case B_MODAL_WINDOW_LOOK:
199			fBorderWidth = 4;
200			break;
201
202		case B_TITLED_WINDOW_LOOK:
203		case B_DOCUMENT_WINDOW_LOOK:
204			hasTab = true;
205			fBorderWidth = 4;
206			break;
207		case B_FLOATING_WINDOW_LOOK:
208			fBorderWidth = 0;
209			hasTab = true;
210			break;
211
212		case B_BORDERED_WINDOW_LOOK:
213			fBorderWidth = 1;
214			break;
215
216		default:
217			fBorderWidth = 0;
218			break;
219	}
220
221	fBorderRect.InsetBy(-fBorderWidth, -fBorderWidth);
222
223	if (hasTab) {
224		font_height fontHeight;
225		fDrawState.Font().GetHeight(fontHeight);
226
227		float tabSize = ceilf(fontHeight.ascent + fontHeight.descent + 4.0);
228
229		if (tabSize < 20)
230			tabSize = 20;
231		fBorderRect.top -= tabSize;
232
233		fTitleBarRect.Set(fFrame.left - 1,
234			fFrame.top - tabSize,
235			((fFrame.right - fFrame.left) < 32.0 ?
236				fFrame.left + 32.0 : fFrame.right) + 1,
237			fFrame.top - 1);
238
239		for (int32 i = 0; i < fTabList.CountItems(); i++) {
240			Decorator::Tab* tab = fTabList.ItemAt(i);
241
242			tab->tabRect = fTitleBarRect;
243
244			const float buttonsInset = 3;
245
246			tab->zoomRect = tab->tabRect;
247			tab->zoomRect.top += buttonsInset;
248			tab->zoomRect.right -= buttonsInset;
249			tab->zoomRect.bottom -= buttonsInset;
250			tab->zoomRect.left = tab->zoomRect.right - tabSize + buttonsInset;
251
252			tab->closeRect = tab->zoomRect;
253			tab->zoomRect.OffsetBy(0 - tab->zoomRect.Width() - 3, 0);
254
255			tab->minimizeRect = tab->zoomRect;
256			tab->minimizeRect.OffsetBy(0 - tab->zoomRect.Width() - 1, 0);
257		}
258	} else {
259		for (int32 i = 0; i < fTabList.CountItems(); i++) {
260			Decorator::Tab* tab = fTabList.ItemAt(i);
261
262			tab->tabRect.Set(0.0, 0.0, -1.0, -1.0);
263			tab->closeRect.Set(0.0, 0.0, -1.0, -1.0);
264			tab->zoomRect.Set(0.0, 0.0, -1.0, -1.0);
265			tab->minimizeRect.Set(0.0, 0.0, -1.0, -1.0);
266		}
267	}
268}
269
270
271void
272WinDecorator::_DrawFrame(BRect rect)
273{
274	if (fTopTab->look == B_NO_BORDER_WINDOW_LOOK)
275		return;
276
277	if (fBorderRect == fFrame)
278		return;
279
280	BRect r = fBorderRect;
281
282	fDrawingEngine->SetHighColor(fFrameLowerColor);
283	fDrawingEngine->StrokeRect(r);
284
285	if (fTopTab->look == B_BORDERED_WINDOW_LOOK)
286		return;
287
288	BPoint pt;
289
290	pt = r.RightTop();
291	pt.x--;
292	fDrawingEngine->StrokeLine(r.LeftTop(), pt, fFrameMidColor);
293	pt = r.LeftBottom();
294	pt.y--;
295	fDrawingEngine->StrokeLine(r.LeftTop(), pt, fFrameMidColor);
296
297	fDrawingEngine->StrokeLine(r.RightTop(), r.RightBottom(), fFrameLowerColor);
298	fDrawingEngine->StrokeLine(r.LeftBottom(), r.RightBottom(), fFrameLowerColor);
299
300	r.InsetBy(1, 1);
301	pt = r.RightTop();
302	pt.x--;
303	fDrawingEngine->StrokeLine(r.LeftTop(),pt,fFrameHighColor);
304	pt = r.LeftBottom();
305	pt.y--;
306	fDrawingEngine->StrokeLine(r.LeftTop(),pt,fFrameHighColor);
307
308	fDrawingEngine->StrokeLine(r.RightTop(), r.RightBottom(), fFrameLowColor);
309	fDrawingEngine->StrokeLine(r.LeftBottom(), r.RightBottom(), fFrameLowColor);
310
311	r.InsetBy(1, 1);
312	fDrawingEngine->StrokeRect(r, fFrameMidColor);
313	r.InsetBy(1, 1);
314	fDrawingEngine->StrokeRect(r, fFrameMidColor);
315}
316
317
318/*!	\brief Actually draws the tab
319
320	This function is called when the tab itself needs drawn. Other items,
321	like the window title or buttons, should not be drawn here.
322
323	\param tab The \a tab to update.
324	\param rect The area of the \a tab to update.
325*/
326void
327WinDecorator::_DrawTab(Decorator::Tab* tab, BRect rect)
328{
329	const BRect& tabRect = tab->tabRect;
330
331	// If a window has a tab, this will draw it and any buttons in it.
332	if (!tabRect.IsValid() || !rect.Intersects(tabRect)
333		|| fTopTab->look == B_NO_BORDER_WINDOW_LOOK) {
334		return;
335	}
336
337	fDrawingEngine->FillRect(tabRect & rect, fTabColor);
338
339	_DrawTitle(tab, tabRect);
340
341	// Draw the buttons if we're supposed to
342	// TODO : we should still draw the buttons if they are disabled, but grey them out
343	_DrawButtons(tab, rect);
344}
345
346
347/*!	\brief Actually draws the title
348
349	The main tasks for this function are to ensure that the decorator draws
350	the title only in its own area and drawing the title itself.
351	Using B_OP_COPY for drawing the title is recommended because of the marked
352	performance hit of the other drawing modes, but it is not a requirement.
353
354	\param tab The \a tab to update.
355	\param rect area of the title to update.
356*/
357void
358WinDecorator::_DrawTitle(Decorator::Tab* tab, BRect rect)
359{
360	const BRect& tabRect = tab->tabRect;
361	const BRect& minimizeRect = tab->minimizeRect;
362	const BRect& zoomRect = tab->zoomRect;
363	const BRect& closeRect = tab->closeRect;
364
365	fDrawingEngine->SetHighColor(fTextColor);
366	fDrawingEngine->SetLowColor(IsFocus(tab)
367		? fFocusTabColor : fNonFocusTabColor);
368
369	tab->truncatedTitle = Title(tab);
370	fDrawState.Font().TruncateString(&tab->truncatedTitle, B_TRUNCATE_END,
371		((minimizeRect.IsValid() ? minimizeRect.left :
372			zoomRect.IsValid() ? zoomRect.left :
373			closeRect.IsValid() ? closeRect.left : tabRect.right) - 5)
374		- (tabRect.left + 5));
375	tab->truncatedTitleLength = tab->truncatedTitle.Length();
376	fDrawingEngine->SetFont(fDrawState.Font());
377
378	font_height fontHeight;
379	fDrawState.Font().GetHeight(fontHeight);
380
381	BPoint titlePos;
382	titlePos.x = tabRect.left + 5;
383	titlePos.y = floorf(((tabRect.top + 2.0) + tabRect.bottom
384			+ fontHeight.ascent + fontHeight.descent) / 2.0
385			- fontHeight.descent + 0.5);
386
387	fDrawingEngine->DrawString(tab->truncatedTitle, tab->truncatedTitleLength,
388		titlePos);
389}
390
391
392void
393WinDecorator::_DrawButtons(Decorator::Tab* tab, const BRect& invalid)
394{
395	if ((tab->flags & B_NOT_MINIMIZABLE) == 0
396		&& invalid.Intersects(tab->minimizeRect)) {
397		_DrawMinimize(tab, false, tab->minimizeRect);
398	}
399	if ((tab->flags & B_NOT_ZOOMABLE) == 0
400		&& invalid.Intersects(tab->zoomRect)) {
401		_DrawZoom(tab, false, tab->zoomRect);
402	}
403	if ((tab->flags & B_NOT_CLOSABLE) == 0
404		&& invalid.Intersects(tab->closeRect)) {
405		_DrawClose(tab, false, tab->closeRect);
406	}
407}
408
409
410/*!
411	\brief Actually draws the minimize button
412
413	Unless a subclass has a particularly large button, it is probably
414	unnecessary to check the update rectangle.
415
416	\param tab The \a tab to update.
417	\param direct Draw without double buffering.
418	\param rect The area of the button to update.
419*/
420void
421WinDecorator::_DrawMinimize(Decorator::Tab* tab, bool direct, BRect rect)
422{
423	// Just like DrawZoom, but for a Minimize button
424	_DrawBeveledRect(rect, tab->minimizePressed);
425
426	fDrawingEngine->SetHighColor(fTextColor);
427	BRect minimizeBox(rect.left + 5, rect.bottom - 4, rect.right - 5,
428		rect.bottom - 3);
429	if (true)
430		minimizeBox.OffsetBy(1, 1);
431
432	fDrawingEngine->SetHighColor(RGBColor(0, 0, 0));
433	fDrawingEngine->StrokeRect(minimizeBox);
434}
435
436
437/*!	\brief Actually draws the zoom button
438
439	Unless a subclass has a particularly large button, it is probably
440	unnecessary to check the update rectangle.
441
442	\param tab The \a tab to update.
443	\param direct Draw without double buffering.
444	\param rect The area of the button to update.
445*/
446void
447WinDecorator::_DrawZoom(Decorator::Tab* tab, bool direct, BRect rect)
448{
449	_DrawBeveledRect(rect, tab->zoomPressed);
450
451	// Draw the Zoom box
452
453	BRect zoomBox(rect);
454	zoomBox.InsetBy(2, 2);
455	zoomBox.InsetBy(1, 0);
456	zoomBox.bottom--;
457	zoomBox.right--;
458
459	if (true)
460		zoomBox.OffsetBy(1, 1);
461
462	fDrawingEngine->SetHighColor(RGBColor(0,0,0));
463	fDrawingEngine->StrokeRect(zoomBox);
464	zoomBox.InsetBy(1, 1);
465	fDrawingEngine->StrokeLine(zoomBox.LeftTop(), zoomBox.RightTop());
466}
467
468
469/*!	\brief Actually draws the close button
470
471	Unless a subclass has a particularly large button, it is probably
472	unnecessary to check the update rectangle.
473
474	\param tab The \a tab to update.
475	\param direct Draw without double buffering.
476	\param rect The area of the button to update.
477*/
478void
479WinDecorator::_DrawClose(Decorator::Tab* tab, bool direct, BRect rect)
480{
481	STRACE(("_DrawClose(%f, %f, %f, %f)\n", rect.left, rect.top, rect.right,
482		rect.bottom));
483
484	// Just like DrawZoom, but for a close button
485	_DrawBeveledRect(rect, tab->closePressed);
486
487	// Draw the X
488
489	BRect closeBox(rect);
490	closeBox.InsetBy(4, 4);
491	closeBox.right--;
492	closeBox.top--;
493
494	if (true)
495		closeBox.OffsetBy(1, 1);
496
497	fDrawingEngine->SetHighColor(RGBColor(0, 0, 0));
498	fDrawingEngine->StrokeLine(closeBox.LeftTop(), closeBox.RightBottom());
499	fDrawingEngine->StrokeLine(closeBox.RightTop(), closeBox.LeftBottom());
500	closeBox.OffsetBy(1, 0);
501	fDrawingEngine->StrokeLine(closeBox.LeftTop(), closeBox.RightBottom());
502	fDrawingEngine->StrokeLine(closeBox.RightTop(), closeBox.LeftBottom());
503}
504
505
506void
507WinDecorator::_SetTitle(Decorator::Tab* tab, const char* string,
508	BRegion* updateRegion)
509{
510	// TODO we could be much smarter about the update region
511
512	BRect rect = TabRect(tab);
513
514	if (updateRegion == NULL)
515		return;
516
517	BRect updatedRect = TabRect(tab);
518	if (rect.left > updatedRect.left)
519		rect.left = updatedRect.left;
520	if (rect.right < updatedRect.right)
521		rect.right = updatedRect.right;
522
523	updateRegion->Include(rect);
524}
525
526
527void
528WinDecorator::_SetFocus(Decorator::Tab* tab)
529{
530	// TODO stub
531
532	// SetFocus() performs necessary duties for color swapping and
533	// other things when a window is deactivated or activated.
534
535	if (IsFocus(tab)) {
536		fTabColor = fFocusTabColor;
537		fTextColor = fFocusTextColor;
538	} else {
539		fTabColor = fNonFocusTabColor;
540		fTextColor = fNonFocusTextColor;
541	}
542}
543
544
545void
546WinDecorator::_MoveBy(BPoint offset)
547{
548	// Move all tab rectangles over
549	for (int32 i = 0; i < fTabList.CountItems(); i++) {
550		Decorator::Tab* tab = fTabList.ItemAt(i);
551
552		tab->zoomRect.OffsetBy(offset);
553		tab->closeRect.OffsetBy(offset);
554		tab->minimizeRect.OffsetBy(offset);
555		tab->tabRect.OffsetBy(offset);
556	}
557
558	// Move all internal rectangles over
559	fFrame.OffsetBy(offset);
560	fTitleBarRect.OffsetBy(offset);
561	fResizeRect.OffsetBy(offset);
562	fBorderRect.OffsetBy(offset);
563}
564
565
566void
567WinDecorator::_ResizeBy(BPoint offset, BRegion* dirty)
568{
569	// Move all internal rectangles the appropriate amount
570	fFrame.right += offset.x;
571	fFrame.bottom += offset.y;
572	fTitleBarRect.right += offset.x;
573	fTitleBarRect.bottom += offset.y;
574	fResizeRect.OffsetBy(offset);
575	fBorderRect.right += offset.x;
576	fBorderRect.bottom += offset.y;
577
578	for (int32 i = 0; i < fTabList.CountItems(); i++) {
579		Decorator::Tab* tab = fTabList.ItemAt(i);
580
581		tab->tabRect.right += offset.x;
582		if (dirty != NULL)
583			dirty->Include(tab->tabRect);
584		//tab->zoomRect.right += offset.x;
585		//tab->closeRect.right += offset.x;
586		//tab->minimizeRect.right += offset.x;
587	}
588	if (dirty != NULL)
589		dirty->Include(fBorderRect);
590
591	// TODO probably some other layouting stuff here
592	_DoLayout();
593}
594
595
596// TODO : _SetSettings
597
598
599Decorator::Tab*
600WinDecorator::_AllocateNewTab()
601{
602	Decorator::Tab* tab = new(std::nothrow) Decorator::Tab;
603	if (tab == NULL)
604		return NULL;
605
606	// Set appropriate colors based on the current focus value. In this case,
607	// each decorator defaults to not having the focus.
608	_SetFocus(tab);
609	return tab;
610}
611
612
613bool
614WinDecorator::_AddTab(DesktopSettings& settings, int32 index,
615	BRegion* updateRegion)
616{
617	_UpdateFont(settings);
618
619	_DoLayout();
620	if (updateRegion != NULL)
621		updateRegion->Include(fTitleBarRect);
622
623	return true;
624}
625
626
627bool
628WinDecorator::_RemoveTab(int32 index, BRegion* updateRegion)
629{
630	BRect oldTitle = fTitleBarRect;
631	_DoLayout();
632	if (updateRegion != NULL) {
633		updateRegion->Include(oldTitle);
634		updateRegion->Include(fTitleBarRect);
635	}
636	return true;
637}
638
639
640bool
641WinDecorator::_MoveTab(int32 from, int32 to, bool isMoving,
642	BRegion* updateRegion)
643{
644	// TODO stub
645	return false;
646}
647
648
649void
650WinDecorator::_GetFootprint(BRegion* region)
651{
652	// This function calculates the decorator's footprint in coordinates
653	// relative to the view. This is most often used to set a Window
654	// object's visible region.
655	if (region == NULL)
656		return;
657
658	region->MakeEmpty();
659
660	if (fTopTab->look == B_NO_BORDER_WINDOW_LOOK)
661		return;
662
663	region->Set(fBorderRect);
664	for (int32 i = 0; i < fTabList.CountItems(); i++) {
665		Decorator::Tab* tab = fTabList.ItemAt(i);
666		region->Include(tab->tabRect);
667	}
668	region->Exclude(fFrame);
669}
670
671
672void
673WinDecorator::_UpdateFont(DesktopSettings& settings)
674{
675	ServerFont font;
676	if (fTopTab->look == B_FLOATING_WINDOW_LOOK)
677		settings.GetDefaultPlainFont(font);
678	else
679		settings.GetDefaultBoldFont(font);
680
681	font.SetFlags(B_FORCE_ANTIALIASING);
682	font.SetSpacing(B_STRING_SPACING);
683	fDrawState.SetFont(font);
684}
685
686
687void
688WinDecorator::_DrawBeveledRect(BRect r, bool down)
689{
690	RGBColor higher;
691	RGBColor high;
692	RGBColor mid;
693	RGBColor low;
694	RGBColor lower;
695
696	if (down) {
697		lower.SetColor(255,255,255);
698		low.SetColor(216,216,216);
699		mid.SetColor(192,192,192);
700		high.SetColor(128,128,128);
701		higher.SetColor(0,0,0);
702	} else {
703		higher.SetColor(255,255,255);
704		high.SetColor(216,216,216);
705		mid.SetColor(192,192,192);
706		low.SetColor(128,128,128);
707		lower.SetColor(0,0,0);
708	}
709
710	BRect rect(r);
711	BPoint point;
712
713	// Top highlight
714	fDrawingEngine->SetHighColor(higher);
715	fDrawingEngine->StrokeLine(rect.LeftTop(), rect.RightTop());
716
717	// Left highlight
718	fDrawingEngine->StrokeLine(rect.LeftTop(), rect.LeftBottom());
719
720	// Right shading
721	point = rect.RightTop();
722	point.y++;
723	fDrawingEngine->StrokeLine(point, rect.RightBottom(), lower);
724
725	// Bottom shading
726	point = rect.LeftBottom();
727	point.x++;
728	fDrawingEngine->StrokeLine(point, rect.RightBottom(), lower);
729
730	rect.InsetBy(1,1);
731	fDrawingEngine->SetHighColor(high);
732	// Top inside highlight
733	fDrawingEngine->StrokeLine(rect.LeftTop(), rect.RightTop());
734
735	// Left inside highlight
736	fDrawingEngine->StrokeLine(rect.LeftTop(), rect.LeftBottom());
737
738	// Right inside shading
739	point = rect.RightTop();
740	point.y++;
741	fDrawingEngine->StrokeLine(point, rect.RightBottom(), low);
742
743	// Bottom inside shading
744	point = rect.LeftBottom();
745	point.x++;
746	fDrawingEngine->StrokeLine(point, rect.RightBottom(), low);
747
748	rect.InsetBy(1,1);
749
750	fDrawingEngine->FillRect(rect, mid);
751}
752
753
754// #pragma mark - DecorAddOn
755
756
757extern "C" DecorAddOn*
758instantiate_decor_addon(image_id id, const char* name)
759{
760	return new (std::nothrow)WinDecorAddOn(id, name);
761}
762