xref: /haiku/src/kits/interface/ScrollBar.cpp (revision 93aeb8c3bc3f13cb1f282e3e749258a23790d947)
1 /*
2  * Copyright (c) 2001-2005, Haiku, Inc.
3  * Distributed under the terms of the MIT license.
4  *
5  * Authors:
6  *		Marc Flerackers (mflerackers@androme.be)
7  *		DarkWyrm (bpmagic@columbus.rr.com)
8  *		Stefano Ceccherini (burton666@libero.it)
9  *		Stephan Aßmus <superstippi@gmx.de>
10  */
11 
12 #include <stdio.h>
13 #include <stdlib.h>
14 #include <string.h>
15 
16 #include <Message.h>
17 #include <OS.h>
18 #include <Shape.h>
19 #include <Window.h>
20 
21 #include <ScrollBar.h>
22 
23 //#define TEST_MODE
24 
25 typedef enum {
26 	ARROW_LEFT = 0,
27 	ARROW_RIGHT,
28 	ARROW_UP,
29 	ARROW_DOWN,
30 	ARROW_NONE
31 } arrow_direction;
32 
33 #define SBC_SCROLLBYVALUE 0
34 #define SBC_SETDOUBLE 1
35 #define SBC_SETPROPORTIONAL 2
36 #define SBC_SETSTYLE 3
37 
38 // Quick constants for determining which arrow is down and are defined with respect
39 // to double arrow mode. ARROW1 and ARROW4 refer to the outer pair of arrows and
40 // ARROW2 and ARROW3 refer to the inner ones. ARROW1 points left/up and ARROW4
41 // points right/down.
42 #define ARROW1 0
43 #define ARROW2 1
44 #define ARROW3 2
45 #define ARROW4 3
46 #define THUMB 4
47 #define NOARROW -1
48 
49 // Because the R5 version kept a lot of data on server-side, we need to kludge our way
50 // into binary compatibility
51 class BScrollBar::Private {
52 public:
53 	Private(BScrollBar* scrollBar)
54 		:
55 		fScrollBar(scrollBar),
56 		fEnabled(true),
57 		fRepeaterThread(-1),
58 		fExitRepeater(false),
59 		fThumbFrame(0.0, 0.0, -1.0, -1.0),
60 		fDoRepeat(false),
61 		fClickOffset(0.0, 0.0),
62 		fThumbInc(0.0),
63 		fStopValue(0.0),
64 		fUpArrowsEnabled(true),
65 		fDownArrowsEnabled(true),
66 		fButtonDown(NOARROW)
67 	{
68 		#ifdef TEST_MODE
69 			fScrollBarInfo.proportional = true;
70 			fScrollBarInfo.double_arrows = true;
71 			fScrollBarInfo.knob = 0;
72 			fScrollBarInfo.min_knob_size = 15;
73 		#else
74 			get_scroll_bar_info(&fScrollBarInfo);
75 		#endif
76 	}
77 
78 	~Private()
79 	{
80 		if (fRepeaterThread >= 0) {
81 			status_t dummy;
82 			fExitRepeater = true;
83 			wait_for_thread(fRepeaterThread, &dummy);
84 		}
85 	}
86 
87 	void DrawScrollBarButton(BScrollBar *owner, arrow_direction direction,
88 							 BRect frame, bool down = false);
89 
90 	static int32 button_repeater_thread(void* data);
91 
92 			int32 ButtonRepeaterThread();
93 
94 	BScrollBar*			fScrollBar;
95 	bool				fEnabled;
96 
97 	// TODO: This should be a static, initialized by
98 	// _init_interface_kit() at application startup-time,
99 	// like BMenu::sMenuInfo
100 	scroll_bar_info		fScrollBarInfo;
101 
102 	thread_id			fRepeaterThread;
103 	volatile bool		fExitRepeater;
104 
105 	BRect				fThumbFrame;
106 	volatile bool		fDoRepeat;
107 	BPoint				fClickOffset;
108 
109 	float				fThumbInc;
110 	float				fStopValue;
111 
112 	bool				fUpArrowsEnabled;
113 	bool				fDownArrowsEnabled;
114 
115 	int8				fButtonDown;
116 };
117 
118 // This thread is spawned when a button is initially pushed and repeatedly scrolls
119 // the scrollbar by a little bit after a short delay
120 int32
121 BScrollBar::Private::button_repeater_thread(void *data)
122 {
123 	BScrollBar::Private* privateData = (BScrollBar::Private*)data;
124 	return privateData->ButtonRepeaterThread();
125 }
126 
127 int32
128 BScrollBar::Private::ButtonRepeaterThread()
129 {
130 	// wait a bit before auto scrolling starts
131 	snooze(500000);
132 
133 	// repeat loop
134 	while (!fExitRepeater) {
135 		if (fScrollBar->LockLooper()) {
136 
137 			if (fDoRepeat) {
138 				float value = fScrollBar->Value() + fThumbInc;
139 				if (fButtonDown == NOARROW) {
140 					// in this case we want to stop when we're under the mouse
141 					if (fThumbInc > 0.0 && value <= fStopValue)
142 						fScrollBar->SetValue(value);
143 					if (fThumbInc < 0.0 && value >= fStopValue)
144 						fScrollBar->SetValue(value);
145 				} else {
146 					fScrollBar->SetValue(value);
147 				}
148 			}
149 
150 			fScrollBar->UnlockLooper();
151 		}
152 
153 		snooze(25000);
154 	}
155 
156 	// tell scrollbar we're gone
157 	if (fScrollBar->LockLooper()) {
158 		fRepeaterThread = -1;
159 		fScrollBar->UnlockLooper();
160 	}
161 
162 	return 0;
163 }
164 
165 
166 BScrollBar::BScrollBar(BRect frame, const char* name, BView *target,
167 					   float min, float max, orientation direction)
168  : BView(frame, name, B_FOLLOW_NONE, B_WILL_DRAW | B_FULL_UPDATE_ON_RESIZE | B_FRAME_EVENTS),
169 	fMin(min),
170 	fMax(max),
171 	fSmallStep(1),
172 	fLargeStep(10),
173 	fValue(0),
174 	fProportion(0.0),
175 	fTarget(NULL),
176 	fOrientation(direction),
177 	fTargetName(NULL)
178 {
179 	SetViewColor(B_TRANSPARENT_COLOR);
180 
181 	fPrivateData = new BScrollBar::Private(this);
182 
183 	SetTarget(target);
184 
185 	_UpdateThumbFrame();
186 	_UpdateArrowButtons();
187 
188 	SetResizingMode((direction == B_VERTICAL) ?
189 		B_FOLLOW_TOP_BOTTOM | B_FOLLOW_RIGHT :
190 		B_FOLLOW_LEFT_RIGHT | B_FOLLOW_BOTTOM );
191 
192 }
193 
194 
195 BScrollBar::BScrollBar(BMessage *data)
196  : BView(data)
197 {
198 }
199 
200 
201 BScrollBar::~BScrollBar()
202 {
203 	delete fPrivateData;
204 	free(fTargetName);
205 	// TODO: detaching from target is currently done in DetachedFromWindow(),
206 	// maybe it should be done here instead?
207 }
208 
209 // Instantiate
210 BArchivable*
211 BScrollBar::Instantiate(BMessage *data)
212 {
213 	// TODO: Implement
214 	return NULL;
215 }
216 
217 // Archive
218 status_t
219 BScrollBar::Archive(BMessage *data, bool deep) const
220 {
221 	BView::Archive(data,deep);
222 	data->AddFloat("_range",fMin);
223 	data->AddFloat("_range",fMax);
224 	data->AddFloat("_steps",fSmallStep);
225 	data->AddFloat("_steps",fLargeStep);
226 	data->AddFloat("_val",fValue);
227 	data->AddInt32("_orient",(int32)fOrientation);
228 	data->AddInt32("_prop",fProportion);
229 
230 	return B_OK;
231 }
232 
233 // AttachedToWindow
234 void
235 BScrollBar::AttachedToWindow()
236 {
237 	// R5's SB contacts the server if fValue!=0. I *think* we don't need to do anything here...
238 }
239 
240 // SetValue
241 void
242 BScrollBar::SetValue(float value)
243 {
244 	if (value > fMax)
245 		value = fMax;
246 	if (value < fMin)
247 		value = fMin;
248 
249 	if (fValue != value) {
250 		fValue = value;
251 		ValueChanged(fValue);
252 	}
253 }
254 
255 // Value
256 float
257 BScrollBar::Value() const
258 {
259 	return fValue;
260 }
261 
262 // SetProportion
263 void
264 BScrollBar::SetProportion(float value)
265 {
266 	if (value < 0.0)
267 		value = 0.0;
268 	if (value > 1.0)
269 		value = 1.0;
270 
271 	if (value != fProportion) {
272 		fProportion = value;
273 
274 		_UpdateThumbFrame();
275 	}
276 }
277 
278 // Proportion
279 float
280 BScrollBar::Proportion() const
281 {
282 	return fProportion;
283 }
284 
285 // ValueChanged
286 void
287 BScrollBar::ValueChanged(float newValue)
288 {
289 /*
290 	From the BeBook:
291 
292 Responds to a notification that the value of the scroll bar has changed to
293 newValue. For a horizontal scroll bar, this function interprets newValue
294 as the coordinate value that should be at the left side of the target
295 view's bounds rectangle. For a vertical scroll bar, it interprets
296 newValue as the coordinate value that should be at the top of the rectangle.
297 It calls ScrollTo() to scroll the target's contents into position, unless
298 they have already been scrolled.
299 
300 ValueChanged() is called as the result both of user actions
301 (B_VALUE_CHANGED messages received from the Application Server) and of
302 programmatic ones. Programmatically, scrolling can be initiated by the
303 target view (calling ScrollTo()) or by the BScrollBar
304 (calling SetValue() or SetRange()).
305 
306 In all these cases, the target view and the scroll bars need to be kept
307 in synch. This is done by a chain of function calls: ValueChanged() calls
308 ScrollTo(), which in turn calls SetValue(), which then calls
309 ValueChanged() again. It's up to ValueChanged() to get off this
310 merry-go-round, which it does by checking the target view's bounds
311 rectangle. If newValue already matches the left or top side of the
312 bounds rectangle, if forgoes calling ScrollTo().
313 
314 ValueChanged() does nothing if a target BView hasn't been set—or
315 if the target has been set by name, but the name doesn't correspond to
316 an actual BView within the scroll bar's window.
317 
318 */
319 	if (fTarget) {
320 		// cache target bounds
321 		BRect targetBounds = fTarget->Bounds();
322 		// if vertical, check bounds top and scroll if different from newValue
323 		if (fOrientation == B_VERTICAL && targetBounds.top != newValue) {
324 			fTarget->ScrollTo(targetBounds.left, newValue);
325 		}
326 		// if horizontal, check bounds left and scroll if different from newValue
327 		if (fOrientation == B_HORIZONTAL && targetBounds.left != newValue) {
328 			fTarget->ScrollTo(newValue, targetBounds.top);
329 		}
330 	}
331 	_UpdateThumbFrame();
332 	_UpdateArrowButtons();
333 }
334 
335 // SetRange
336 void
337 BScrollBar::SetRange(float min, float max)
338 {
339 	fMin = min;
340 	fMax = max;
341 
342 	if (fValue > fMax)
343 		fValue = fMax;
344 	else if (fValue < fMin)
345 		fValue = fMin;
346 
347 	Invalidate();
348 
349 	// Just a sort-of hack for now. ValueChanged is called, but with
350 	// what value??
351 	ValueChanged(fValue);
352 }
353 
354 // GetRange
355 void
356 BScrollBar::GetRange(float *min, float *max) const
357 {
358 	if (min != NULL)
359 		*min = fMin;
360 	if (max != NULL)
361 		*max = fMax;
362 }
363 
364 // SetSteps
365 void
366 BScrollBar::SetSteps(float smallStep, float largeStep)
367 {
368 	// Under R5, steps can be set only after being attached to a window, probably because
369 	// the data is kept server-side. We'll just remove that limitation... :P
370 
371 	// The BeBook also says that we need to specify an integer value even though the step
372 	// values are floats. For the moment, we'll just make sure that they are integers
373 	fSmallStep = (int32)smallStep;
374 	fLargeStep = (int32)largeStep;
375 
376 	// TODO: test use of fractional values and make them work properly if they don't
377 }
378 
379 // GetSteps
380 void
381 BScrollBar::GetSteps(float *smallStep, float *largeStep) const
382 {
383 	if (smallStep)
384 		*smallStep = fSmallStep;
385 	if (largeStep)
386 		*largeStep = fLargeStep;
387 }
388 
389 // SetTarget
390 void
391 BScrollBar::SetTarget(BView *target)
392 {
393 	fTarget = target;
394 	free(fTargetName);
395 
396 	if (fTarget) {
397 		fTargetName = strdup(target->Name());
398 
399 		if (fOrientation == B_VERTICAL)
400 			fTarget->fVerScroller = this;
401 		else
402 			fTarget->fHorScroller = this;
403 	} else
404 		fTargetName = NULL;
405 }
406 
407 // SetTarget
408 void
409 BScrollBar::SetTarget(const char *targetName)
410 {
411 	if (!targetName)
412 		return;
413 
414 	if (!Window())
415 		debugger("Method requires window and doesn't have one");
416 
417 	BView *target = Window()->FindView(targetName);
418 	if (target)
419 		SetTarget(target);
420 }
421 
422 // Target
423 BView *
424 BScrollBar::Target() const
425 {
426 	return fTarget;
427 }
428 
429 // Orientation
430 orientation
431 BScrollBar::Orientation() const
432 {
433 	return fOrientation;
434 }
435 
436 // MessageReceived
437 void
438 BScrollBar::MessageReceived(BMessage *msg)
439 {
440 	switch(msg->what) {
441 		case B_VALUE_CHANGED:
442 		{
443 			int32 value;
444 			if (msg->FindInt32("value", &value) == B_OK)
445 				ValueChanged(value);
446 			break;
447 		}
448 		default:
449 			BView::MessageReceived(msg);
450 			break;
451 	}
452 }
453 
454 // MouseDown
455 void
456 BScrollBar::MouseDown(BPoint where)
457 {
458 	if (!fPrivateData->fEnabled || fMin == fMax)
459 		return;
460 
461 	SetMouseEventMask(B_POINTER_EVENTS, B_LOCK_WINDOW_FOCUS);
462 
463 	// hit test for the thumb
464 	if (fPrivateData->fThumbFrame.Contains(where)) {
465 		fPrivateData->fButtonDown = THUMB;
466 		fPrivateData->fClickOffset = fPrivateData->fThumbFrame.LeftTop() - where;
467 		Invalidate(fPrivateData->fThumbFrame);
468 		return;
469 	}
470 printf("check\n");
471 	// hit test for arrows or empty area
472 	float scrollValue = 0.0;
473 	fPrivateData->fButtonDown = _ButtonFor(where);
474 	switch (fPrivateData->fButtonDown) {
475 		case ARROW1:
476 			scrollValue = -fSmallStep;
477 			break;
478 		case ARROW2:
479 			scrollValue = fSmallStep;
480 			break;
481 		case ARROW3:
482 			scrollValue = -fSmallStep;
483 			break;
484 		case ARROW4:
485 			scrollValue = fSmallStep;
486 			break;
487 		case NOARROW:
488 			// we hit the empty area, figure out which side of the thumb
489 			if (fOrientation == B_VERTICAL) {
490 				if (where.y < fPrivateData->fThumbFrame.top)
491 					scrollValue = -fLargeStep;
492 				else
493 					scrollValue = fLargeStep;
494 			} else {
495 				if (where.x < fPrivateData->fThumbFrame.left)
496 					scrollValue = -fLargeStep;
497 				else
498 					scrollValue = fLargeStep;
499 			}
500 			_UpdateTargetValue(where);
501 			break;
502 	}
503 	if (scrollValue != 0.0) {
504 		SetValue(fValue + scrollValue);
505 		Invalidate(_ButtonRectFor(fPrivateData->fButtonDown));
506 
507 		// launch the repeat thread
508 		if (fPrivateData->fRepeaterThread == -1) {
509 			fPrivateData->fExitRepeater = false;
510 			fPrivateData->fThumbInc = scrollValue;
511 			fPrivateData->fDoRepeat = true;
512 			fPrivateData->fRepeaterThread = spawn_thread(fPrivateData->button_repeater_thread,
513 														 "scroll repeater", B_NORMAL_PRIORITY, fPrivateData);
514 			resume_thread(fPrivateData->fRepeaterThread);
515 		}
516 	}
517 }
518 
519 // MouseUp
520 void
521 BScrollBar::MouseUp(BPoint pt)
522 {
523 	if (fPrivateData->fButtonDown == THUMB)
524 		Invalidate(fPrivateData->fThumbFrame);
525 	else
526 		Invalidate(_ButtonRectFor(fPrivateData->fButtonDown));
527 
528 	fPrivateData->fButtonDown = NOARROW;
529 	fPrivateData->fExitRepeater = true;
530 	fPrivateData->fDoRepeat = false;
531 }
532 
533 // MouseMoved
534 void
535 BScrollBar::MouseMoved(BPoint where, uint32 transit, const BMessage* message)
536 {
537 	if (!fPrivateData->fEnabled || fMin >= fMax || fProportion >= 1.0)
538 		return;
539 
540 	if (fPrivateData->fButtonDown != NOARROW) {
541 		if (fPrivateData->fButtonDown == THUMB) {
542 			SetValue(_ValueFor(where + fPrivateData->fClickOffset));
543 		} else {
544 			// suspend the repeating if the mouse is not over the button
545 			bool repeat = _ButtonRectFor(fPrivateData->fButtonDown).Contains(where);
546 			if (fPrivateData->fDoRepeat != repeat) {
547 				fPrivateData->fDoRepeat = repeat;
548 				Invalidate(_ButtonRectFor(fPrivateData->fButtonDown));
549 			}
550 		}
551 	} else {
552 		// update the value at which we want to stop repeating
553 		if (fPrivateData->fDoRepeat) {
554 			_UpdateTargetValue(where);
555 			// we might have to turn arround
556 			if ((fValue < fPrivateData->fStopValue && fPrivateData->fThumbInc < 0) ||
557 				(fValue > fPrivateData->fStopValue && fPrivateData->fThumbInc > 0))
558 				fPrivateData->fThumbInc = -fPrivateData->fThumbInc;
559 		}
560 	}
561 }
562 
563 // DetachedFromWindow
564 void
565 BScrollBar::DetachedFromWindow()
566 {
567 	if (fTarget) {
568 		if (fOrientation == B_VERTICAL)
569 			fTarget->fVerScroller = NULL;
570 		else
571 			fTarget->fHorScroller = NULL;
572 	}
573 
574 	fTarget = NULL;
575 	free(fTargetName);
576 	fTargetName = NULL;
577 }
578 
579 // Draw
580 void
581 BScrollBar::Draw(BRect updateRect)
582 {
583 	BRect bounds = Bounds();
584 
585 	rgb_color normal = ui_color(B_PANEL_BACKGROUND_COLOR);
586 
587 	// stroke a dark frame arround the entire scrollbar (independent of enabled state)
588 	SetHighColor(tint_color(normal, B_DARKEN_2_TINT));
589 	StrokeRect(bounds);
590 	bounds.InsetBy(1.0, 1.0);
591 
592 	bool enabled = fPrivateData->fEnabled && fMin < fMax && fProportion < 1.0;
593 
594 	rgb_color light, light1, dark, dark1, dark2, dark4;
595 	if (enabled) {
596 		light = tint_color(normal, B_LIGHTEN_MAX_TINT);
597 		light1 = tint_color(normal, B_LIGHTEN_1_TINT);
598 		dark = tint_color(normal, B_DARKEN_3_TINT);
599 		dark1 = tint_color(normal, B_DARKEN_1_TINT);
600 		dark2 = tint_color(normal, B_DARKEN_2_TINT);
601 		dark4 = tint_color(normal, B_DARKEN_4_TINT);
602 	} else {
603 		light = tint_color(normal, B_LIGHTEN_MAX_TINT);
604 		light1 = normal;
605 		dark = tint_color(normal, B_DARKEN_2_TINT);
606 		dark1 = tint_color(normal, B_LIGHTEN_2_TINT);
607 		dark2 = tint_color(normal, B_LIGHTEN_1_TINT);
608 		dark4 = tint_color(normal, B_DARKEN_3_TINT);
609 	}
610 
611 	SetDrawingMode(B_OP_OVER);
612 
613 	BRect thumbBG = bounds;
614 	bool doubleArrows = _DoubleArrows();
615 
616 	// Draw arrows
617 	if (fOrientation == B_HORIZONTAL) {
618 		BRect buttonFrame(bounds.left, bounds.top, bounds.left + bounds.Height(), bounds.bottom);
619 
620 		_DrawArrowButton(ARROW_LEFT, doubleArrows, buttonFrame, updateRect,
621 			enabled, fPrivateData->fButtonDown == ARROW1);
622 
623 		if (doubleArrows) {
624 			buttonFrame.OffsetBy(bounds.Height() + 1, 0.0);
625 			_DrawArrowButton(ARROW_RIGHT, doubleArrows, buttonFrame, updateRect,
626 				enabled, fPrivateData->fButtonDown == ARROW2);
627 
628 			buttonFrame.OffsetTo(bounds.right - ((bounds.Height() * 2) + 1), bounds.top);
629 			_DrawArrowButton(ARROW_LEFT, doubleArrows, buttonFrame, updateRect,
630 				enabled, fPrivateData->fButtonDown == ARROW3);
631 
632 			thumbBG.left += bounds.Height() * 2 + 2;
633 			thumbBG.right -= bounds.Height() * 2 + 2;
634 		} else {
635 			thumbBG.left += bounds.Height() + 1;
636 			thumbBG.right -= bounds.Height() + 1;
637 		}
638 
639 		buttonFrame.OffsetTo(bounds.right - bounds.Height(), bounds.top);
640 		_DrawArrowButton(ARROW_RIGHT, doubleArrows, buttonFrame, updateRect,
641 			enabled, fPrivateData->fButtonDown == ARROW4);
642 	} else {
643 		BRect buttonFrame(bounds.left, bounds.top, bounds.right, bounds.top + bounds.Width());
644 
645 		_DrawArrowButton(ARROW_UP, doubleArrows, buttonFrame, updateRect,
646 			enabled, fPrivateData->fButtonDown == ARROW1);
647 
648 		if (doubleArrows) {
649 			buttonFrame.OffsetBy(0.0, bounds.Width() + 1);
650 			_DrawArrowButton(ARROW_DOWN, doubleArrows, buttonFrame, updateRect,
651 				enabled, fPrivateData->fButtonDown == ARROW2);
652 
653 			buttonFrame.OffsetTo(bounds.left, bounds.bottom - ((bounds.Width() * 2) + 1));
654 			_DrawArrowButton(ARROW_UP, doubleArrows, buttonFrame, updateRect,
655 				enabled, fPrivateData->fButtonDown == ARROW3);
656 
657 			thumbBG.top += bounds.Width() * 2 + 2;
658 			thumbBG.bottom -= bounds.Width() * 2 + 2;
659 		} else {
660 			thumbBG.top += bounds.Width() + 1;
661 			thumbBG.bottom -= bounds.Width() + 1;
662 		}
663 
664 		buttonFrame.OffsetTo(bounds.left, bounds.bottom - bounds.Width());
665 		_DrawArrowButton(ARROW_DOWN, doubleArrows, buttonFrame, updateRect,
666 			enabled, fPrivateData->fButtonDown == ARROW4);
667 	}
668 
669 	SetDrawingMode(B_OP_COPY);
670 
671 	// background for thumb area
672 	BRect rect(fPrivateData->fThumbFrame);
673 
674 	// frame
675 	if (fOrientation == B_HORIZONTAL) {
676 		int32 totalLines = 0;
677 		if (rect.left > thumbBG.left)
678 			totalLines += 1;
679 		if (rect.left > thumbBG.left + 1)
680 			totalLines += 3;
681 		if (rect.right < thumbBG.right - 1)
682 			totalLines += 3;
683 		if (rect.right < thumbBG.right)
684 			totalLines += 1;
685 
686 		if (totalLines > 0) {
687 			BeginLineArray(totalLines);
688 				if (rect.left > thumbBG.left) {
689 					AddLine(BPoint(thumbBG.left, thumbBG.bottom),
690 							BPoint(thumbBG.left, thumbBG.top),
691 							rect.left > thumbBG.left + 1 ? dark4 : dark);
692 				}
693 				if (rect.left > thumbBG.left + 1) {
694 					AddLine(BPoint(thumbBG.left + 1, thumbBG.top + 1),
695 							BPoint(thumbBG.left + 1, thumbBG.bottom), dark2);
696 					AddLine(BPoint(thumbBG.left + 1, thumbBG.top),
697 							BPoint(rect.left - 1, thumbBG.top), dark2);
698 					AddLine(BPoint(rect.left - 1, thumbBG.bottom),
699 							BPoint(thumbBG.left + 2, thumbBG.bottom), normal);
700 				}
701 
702 				if (rect.right < thumbBG.right - 1) {
703 					AddLine(BPoint(rect.right + 2, thumbBG.top + 1),
704 							BPoint(rect.right + 2, thumbBG.bottom), dark2);
705 					AddLine(BPoint(rect.right + 1, thumbBG.top),
706 							BPoint(thumbBG.right, thumbBG.top), dark2);
707 					AddLine(BPoint(thumbBG.right - 1, thumbBG.bottom),
708 							BPoint(rect.right + 3, thumbBG.bottom), normal);
709 				}
710 				if (rect.right < thumbBG.right) {
711 					AddLine(BPoint(thumbBG.right, thumbBG.top),
712 							BPoint(thumbBG.right, thumbBG.bottom), dark);
713 				}
714 			EndLineArray();
715 		}
716 	} else {
717 		int32 totalLines = 0;
718 		if (rect.top > thumbBG.top)
719 			totalLines += 1;
720 		if (rect.top > thumbBG.top + 1)
721 			totalLines += 3;
722 		if (rect.bottom < thumbBG.bottom - 1)
723 			totalLines += 3;
724 		if (rect.bottom < thumbBG.bottom)
725 			totalLines += 1;
726 
727 		if (totalLines > 0) {
728 			BeginLineArray(totalLines);
729 				if (rect.top > thumbBG.top) {
730 					AddLine(BPoint(thumbBG.left, thumbBG.top),
731 							BPoint(thumbBG.right, thumbBG.top),
732 							rect.top > thumbBG.top + 1 ? dark4 : dark);
733 				}
734 				if (rect.top > thumbBG.top + 1) {
735 					AddLine(BPoint(thumbBG.left + 1, thumbBG.top + 1),
736 							BPoint(thumbBG.right, thumbBG.top + 1), dark2);
737 					AddLine(BPoint(thumbBG.left, rect.top - 1),
738 							BPoint(thumbBG.left, thumbBG.top + 1), dark2);
739 					AddLine(BPoint(thumbBG.right, rect.top - 1),
740 							BPoint(thumbBG.right, thumbBG.top + 2), normal);
741 				}
742 
743 				if (rect.bottom < thumbBG.bottom - 1) {
744 					AddLine(BPoint(thumbBG.left + 1, rect.bottom + 2),
745 							BPoint(thumbBG.right, rect.bottom + 2), dark2);
746 					AddLine(BPoint(thumbBG.left, rect.bottom + 1),
747 							BPoint(thumbBG.left, thumbBG.bottom - 1), dark2);
748 					AddLine(BPoint(thumbBG.right, rect.bottom + 3),
749 							BPoint(thumbBG.right, thumbBG.bottom - 1), normal);
750 				}
751 				if (rect.bottom < thumbBG.bottom) {
752 					AddLine(BPoint(thumbBG.left, thumbBG.bottom),
753 							BPoint(thumbBG.right, thumbBG.bottom), dark);
754 				}
755 			EndLineArray();
756 		}
757 	}
758 
759 	SetHighColor(dark1);
760 
761 	// Draw scroll thumb
762 	if (enabled) {
763 		// fill and additional dark lines
764 		thumbBG.InsetBy(1.0, 1.0);
765 		if (fOrientation == B_HORIZONTAL) {
766 			BRect leftOfThumb(thumbBG.left + 1, thumbBG.top, rect.left - 1, thumbBG.bottom);
767 			if (leftOfThumb.IsValid())
768 				FillRect(leftOfThumb);
769 
770 			BRect rightOfThumb(rect.right + 3, thumbBG.top, thumbBG.right, thumbBG.bottom);
771 			if (rightOfThumb.IsValid())
772 				FillRect(rightOfThumb);
773 
774 			// dark lines before and after thumb
775 			if (rect.left > thumbBG.left) {
776 				SetHighColor(dark);
777 				StrokeLine(BPoint(rect.left - 1, rect.top), BPoint(rect.left - 1, rect.bottom));
778 			}
779 			if (rect.right < thumbBG.right) {
780 				SetHighColor(dark4);
781 				StrokeLine(BPoint(rect.right + 1, rect.top), BPoint(rect.right + 1, rect.bottom));
782 			}
783 		} else {
784 			BRect topOfThumb(thumbBG.left, thumbBG.top + 1, thumbBG.right, rect.top - 1);
785 			if (topOfThumb.IsValid())
786 				FillRect(topOfThumb);
787 
788 			BRect bottomOfThumb(thumbBG.left, rect.bottom + 3, thumbBG.right, thumbBG.bottom);
789 			if (bottomOfThumb.IsValid())
790 				FillRect(bottomOfThumb);
791 
792 			// dark lines before and after thumb
793 			if (rect.top > thumbBG.top) {
794 				SetHighColor(dark);
795 				StrokeLine(BPoint(rect.left, rect.top - 1), BPoint(rect.right, rect.top - 1));
796 			}
797 			if (rect.bottom < thumbBG.bottom) {
798 				SetHighColor(dark4);
799 				StrokeLine(BPoint(rect.left, rect.bottom + 1), BPoint(rect.right, rect.bottom + 1));
800 			}
801 		}
802 
803 		BeginLineArray(4);
804 			AddLine(BPoint(rect.left, rect.bottom),
805 					BPoint(rect.left, rect.top), light);
806 			AddLine(BPoint(rect.left + 1, rect.top),
807 					BPoint(rect.right, rect.top), light);
808 			AddLine(BPoint(rect.right, rect.top + 1),
809 					BPoint(rect.right, rect.bottom), dark1);
810 			AddLine(BPoint(rect.right - 1, rect.bottom),
811 					BPoint(rect.left + 1, rect.bottom), dark1);
812 		EndLineArray();
813 
814 		// fill
815 		rect.InsetBy(1.0, 1.0);
816 		/*if (fPrivateData->fButtonDown == THUMB)
817 			SetHighColor(tint_color(normal, (B_NO_TINT + B_DARKEN_1_TINT) / 2));
818 		else*/
819 			SetHighColor(normal);
820 
821 		FillRect(rect);
822 
823 		// TODO: Add the other thumb styles - dots and lines
824 	} else {
825 		if (fMin >= fMax || fProportion >= 1.0) {
826 			// we cannot scroll at all
827 			_DrawDisabledBackground(thumbBG, light, dark, dark1);
828 		} else {
829 			// we could scroll, but we're simply disabled
830 			if (fOrientation == B_HORIZONTAL) {
831 				// left of thumb
832 				BRect besidesThumb(thumbBG);
833 				besidesThumb.right = rect.left - 1;
834 				_DrawDisabledBackground(besidesThumb, light, dark, dark1);
835 				// right of thumb
836 				besidesThumb.left = rect.right + 1;
837 				besidesThumb.right = thumbBG.right;
838 				_DrawDisabledBackground(besidesThumb, light, dark, dark1);
839 			} else {
840 				// above thumb
841 				BRect besidesThumb(thumbBG);
842 				besidesThumb.bottom = rect.top - 1;
843 				_DrawDisabledBackground(besidesThumb, light, dark, dark1);
844 				// below thumb
845 				besidesThumb.top = rect.bottom + 1;
846 				besidesThumb.bottom = thumbBG.bottom;
847 				_DrawDisabledBackground(besidesThumb, light, dark, dark1);
848 			}
849 			// thumb
850 			BeginLineArray(4);
851 				AddLine(BPoint(rect.left, rect.bottom),
852 						BPoint(rect.left, rect.top), light);
853 				AddLine(BPoint(rect.left + 1, rect.top),
854 						BPoint(rect.right, rect.top), light);
855 				AddLine(BPoint(rect.right, rect.top + 1),
856 						BPoint(rect.right, rect.bottom), dark2);
857 				AddLine(BPoint(rect.right - 1, rect.bottom),
858 						BPoint(rect.left + 1, rect.bottom), dark2);
859 			EndLineArray();
860 			// fill thumb
861 			rect.InsetBy(1.0, 1.0);
862 			SetHighColor(dark1);
863 			FillRect(rect);
864 		}
865 	}
866 }
867 
868 // FrameMoved
869 void
870 BScrollBar::FrameMoved(BPoint new_position)
871 {
872 }
873 
874 // FrameResized
875 void
876 BScrollBar::FrameResized(float new_width, float new_height)
877 {
878 	_UpdateThumbFrame();
879 }
880 
881 // ResolveSpecifier
882 BHandler*
883 BScrollBar::ResolveSpecifier(BMessage *msg,int32 index,
884 		BMessage *specifier,int32 form,const char *property)
885 {
886 	// TODO: Implement
887 	return NULL;
888 }
889 
890 // ResizeToPreferred
891 void
892 BScrollBar::ResizeToPreferred()
893 {
894 	BView::ResizeToPreferred();
895 }
896 
897 // GetPreferredSize
898 void
899 BScrollBar::GetPreferredSize(float *width, float *height)
900 {
901 	if (fOrientation == B_VERTICAL) {
902 		*width = B_V_SCROLL_BAR_WIDTH;
903 		*height = Bounds().Height();
904 	} else if (fOrientation == B_HORIZONTAL) {
905 		*width = Bounds().Width();
906 		*height = B_H_SCROLL_BAR_HEIGHT;
907 	}
908 }
909 
910 // MakeFocus
911 void
912 BScrollBar::MakeFocus(bool state)
913 {
914 	// TODO: ?!? Really?
915 	if (fTarget)
916 		fTarget->MakeFocus(state);
917 }
918 
919 // AllAttached
920 void
921 BScrollBar::AllAttached()
922 {
923 }
924 
925 // AllDetached
926 void
927 BScrollBar::AllDetached()
928 {
929 }
930 
931 // GetSupportedSuites
932 status_t
933 BScrollBar::GetSupportedSuites(BMessage *data)
934 {
935 	return B_ERROR;
936 }
937 
938 // Perform
939 status_t
940 BScrollBar::Perform(perform_code d, void *arg)
941 {
942 	return BView::Perform(d, arg);
943 }
944 
945 #if DISABLES_ON_WINDOW_DEACTIVATION
946 void
947 BScrollBar::WindowActivated(bool active)
948 {
949 	fPrivateData->fEnabled = active;
950 	Invalidate();
951 }
952 #endif // DISABLES_ON_WINDOW_DEACTIVATION
953 
954 void BScrollBar::_ReservedScrollBar1() {}
955 void BScrollBar::_ReservedScrollBar2() {}
956 void BScrollBar::_ReservedScrollBar3() {}
957 void BScrollBar::_ReservedScrollBar4() {}
958 
959 // operator=
960 BScrollBar &
961 BScrollBar::operator=(const BScrollBar &)
962 {
963 	return *this;
964 }
965 
966 // _DoubleArrows
967 bool
968 BScrollBar::_DoubleArrows() const
969 {
970 	if (!fPrivateData->fScrollBarInfo.double_arrows)
971 		return false;
972 
973 	// if there is not enough room, switch to single arrows even though
974 	// double arrows is specified
975 	if (fOrientation == B_HORIZONTAL)
976 		return Bounds().Width() > (Bounds().Height() + 1) * 4 + fPrivateData->fScrollBarInfo.min_knob_size * 2;
977 	else
978 		return Bounds().Height() > (Bounds().Width() + 1) * 4 + fPrivateData->fScrollBarInfo.min_knob_size * 2;
979 }
980 
981 // _UpdateThumbFrame
982 void
983 BScrollBar::_UpdateThumbFrame()
984 {
985 	BRect bounds = Bounds();
986 	bounds.InsetBy(1.0, 1.0);
987 
988 	BRect oldFrame = fPrivateData->fThumbFrame;
989 	fPrivateData->fThumbFrame = bounds;
990 	float minSize = fPrivateData->fScrollBarInfo.min_knob_size;
991 	float maxSize;
992 	float buttonSize;
993 
994 	// assume square buttons
995 	if (fOrientation == B_VERTICAL) {
996 		maxSize = bounds.Height();
997 		buttonSize = bounds.Width() + 1.0;
998 	} else {
999 		maxSize = bounds.Width();
1000 		buttonSize = bounds.Height() + 1.0;
1001 	}
1002 
1003 	if (_DoubleArrows()) {
1004 		// subtract the size of four buttons
1005 		maxSize -= buttonSize * 4;
1006 	} else {
1007 		// subtract the size of two buttons
1008 		maxSize -= buttonSize * 2;
1009 	}
1010 	// visual adjustments (room for darker line between thumb and buttons)
1011 	maxSize--;
1012 
1013 	float thumbSize = minSize;
1014 	float proportion = fProportion;
1015 	if (fMin == fMax)
1016 		proportion = 1.0;
1017 	if (fPrivateData->fScrollBarInfo.proportional)
1018 		thumbSize += (maxSize - minSize) * proportion;
1019 	thumbSize = floorf(thumbSize + 0.5);
1020 	thumbSize--;
1021 
1022 	// the thumb can be scrolled within the remaining area "maxSize - thumbSize"
1023 	float offset = floorf(((fValue - fMin) / (fMax - fMin + 1.0)) * (maxSize - thumbSize));
1024 
1025 	if (_DoubleArrows()) {
1026 		offset += buttonSize * 2;
1027 	} else {
1028 		offset += buttonSize;
1029 	}
1030 	// visual adjustments (room for darker line between thumb and buttons)
1031 	offset++;
1032 
1033 	if (fOrientation == B_VERTICAL) {
1034 		fPrivateData->fThumbFrame.bottom = fPrivateData->fThumbFrame.top + thumbSize;
1035 		fPrivateData->fThumbFrame.OffsetBy(0.0, offset);
1036 	} else {
1037 		fPrivateData->fThumbFrame.right = fPrivateData->fThumbFrame.left + thumbSize;
1038 		fPrivateData->fThumbFrame.OffsetBy(offset, 0.0);
1039 	}
1040 
1041 	if (Window()) {
1042 		BRect invalid = oldFrame.IsValid() ? oldFrame | fPrivateData->fThumbFrame : fPrivateData->fThumbFrame;
1043 		// account for those two dark lines
1044 		if (fOrientation == B_HORIZONTAL)
1045 			invalid.InsetBy(-2.0, 0.0);
1046 		else
1047 			invalid.InsetBy(0.0, -2.0);
1048 		Invalidate(invalid);
1049 	}
1050 }
1051 
1052 // _ValueFor
1053 float
1054 BScrollBar::_ValueFor(BPoint where) const
1055 {
1056 	BRect bounds = Bounds();
1057 	bounds.InsetBy(1.0, 1.0);
1058 
1059 	float offset;
1060 	float thumbSize;
1061 	float maxSize;
1062 	float buttonSize;
1063 
1064 	if (fOrientation == B_VERTICAL) {
1065 		offset = where.y;
1066 		thumbSize = fPrivateData->fThumbFrame.Height();
1067 		maxSize = bounds.Height();
1068 		buttonSize = bounds.Width() + 1.0;
1069 	} else {
1070 		offset = where.x;
1071 		thumbSize = fPrivateData->fThumbFrame.Width();
1072 		maxSize = bounds.Width();
1073 		buttonSize = bounds.Height() + 1.0;
1074 	}
1075 
1076 	if (_DoubleArrows()) {
1077 		// subtract the size of four buttons
1078 		maxSize -= buttonSize * 4;
1079 		// convert point to inside of area between buttons
1080 		offset -= buttonSize * 2;
1081 	} else {
1082 		// subtract the size of two buttons
1083 		maxSize -= buttonSize * 2;
1084 		// convert point to inside of area between buttons
1085 		offset -= buttonSize;
1086 	}
1087 	// visual adjustments (room for darker line between thumb and buttons)
1088 	maxSize--;
1089 	offset++;
1090 
1091 	float value = fMin + (offset / (maxSize - thumbSize) * (fMax - fMin + 1.0));
1092 	if (value >= 0.0)
1093 		return floorf(value + 0.5);
1094 	else
1095 		return ceilf(value - 0.5);
1096 }
1097 
1098 // _ButtonFor
1099 int32
1100 BScrollBar::_ButtonFor(BPoint where) const
1101 {
1102 	BRect bounds = Bounds();
1103 	bounds.InsetBy(1.0, 1.0);
1104 
1105 	float buttonSize;
1106 	if (fOrientation == B_VERTICAL) {
1107 		buttonSize = bounds.Width() + 1.0;
1108 	} else {
1109 		buttonSize = bounds.Height() + 1.0;
1110 	}
1111 
1112 	BRect rect(bounds.left, bounds.top,
1113 			   bounds.left + buttonSize - 1.0, bounds.top + buttonSize - 1.0);
1114 
1115 	if (fOrientation == B_VERTICAL) {
1116 		if (rect.Contains(where))
1117 			return ARROW1;
1118 		if (_DoubleArrows()) {
1119 			rect.OffsetBy(0.0, buttonSize);
1120 			if (rect.Contains(where))
1121 				return ARROW2;
1122 			rect.OffsetTo(bounds.left, bounds.bottom - 2 * buttonSize);
1123 			if (rect.Contains(where))
1124 				return ARROW3;
1125 		}
1126 		rect.OffsetTo(bounds.left, bounds.bottom - buttonSize);
1127 		if (rect.Contains(where))
1128 			return ARROW4;
1129 	} else {
1130 		if (rect.Contains(where))
1131 			return ARROW1;
1132 		if (_DoubleArrows()) {
1133 			rect.OffsetBy(buttonSize, 0.0);
1134 			if (rect.Contains(where))
1135 				return ARROW2;
1136 			rect.OffsetTo(bounds.right - 2 * buttonSize, bounds.top);
1137 			if (rect.Contains(where))
1138 				return ARROW3;
1139 		}
1140 		rect.OffsetTo(bounds.right - buttonSize, bounds.top);
1141 		if (rect.Contains(where))
1142 			return ARROW4;
1143 	}
1144 
1145 	return NOARROW;
1146 }
1147 
1148 // _ButtonRectFor
1149 BRect
1150 BScrollBar::_ButtonRectFor(int32 button) const
1151 {
1152 	BRect bounds = Bounds();
1153 	bounds.InsetBy(1.0, 1.0);
1154 
1155 	float buttonSize;
1156 	if (fOrientation == B_VERTICAL) {
1157 		buttonSize = bounds.Width() + 1.0;
1158 	} else {
1159 		buttonSize = bounds.Height() + 1.0;
1160 	}
1161 
1162 	BRect rect(bounds.left, bounds.top,
1163 			   bounds.left + buttonSize - 1.0, bounds.top + buttonSize - 1.0);
1164 
1165 	if (fOrientation == B_VERTICAL) {
1166 		switch (button) {
1167 			case ARROW1:
1168 				break;
1169 			case ARROW2:
1170 				rect.OffsetBy(0.0, buttonSize);
1171 				break;
1172 			case ARROW3:
1173 				rect.OffsetTo(bounds.left, bounds.bottom - 2 * buttonSize + 1);
1174 				break;
1175 			case ARROW4:
1176 				rect.OffsetTo(bounds.left, bounds.bottom - buttonSize + 1);
1177 				break;
1178 		}
1179 	} else {
1180 		switch (button) {
1181 			case ARROW1:
1182 				break;
1183 			case ARROW2:
1184 				rect.OffsetBy(buttonSize, 0.0);
1185 				break;
1186 			case ARROW3:
1187 				rect.OffsetTo(bounds.right - 2 * buttonSize + 1, bounds.top);
1188 				break;
1189 			case ARROW4:
1190 				rect.OffsetTo(bounds.right - buttonSize + 1, bounds.top);
1191 				break;
1192 		}
1193 	}
1194 
1195 	return rect;
1196 }
1197 
1198 // _UpdateTargetValue
1199 void
1200 BScrollBar::_UpdateTargetValue(BPoint where)
1201 {
1202 	if (fOrientation == B_VERTICAL)
1203 		fPrivateData->fStopValue = _ValueFor(BPoint(where.x, where.y - fPrivateData->fThumbFrame.Height() / 2.0));
1204 	else
1205 		fPrivateData->fStopValue = _ValueFor(BPoint(where.x - fPrivateData->fThumbFrame.Width() / 2.0, where.y));
1206 }
1207 
1208 // _UpdateArrowButtons
1209 void
1210 BScrollBar::_UpdateArrowButtons()
1211 {
1212 	bool upEnabled = fValue > fMin;
1213 	if (fPrivateData->fUpArrowsEnabled != upEnabled) {
1214 		fPrivateData->fUpArrowsEnabled = upEnabled;
1215 		Invalidate(_ButtonRectFor(ARROW1));
1216 		if (_DoubleArrows())
1217 			Invalidate(_ButtonRectFor(ARROW3));
1218 	}
1219 
1220 	bool downEnabled = fValue < fMax;
1221 	if (fPrivateData->fDownArrowsEnabled != downEnabled) {
1222 		fPrivateData->fDownArrowsEnabled = downEnabled;
1223 		Invalidate(_ButtonRectFor(ARROW4));
1224 		if (_DoubleArrows())
1225 			Invalidate(_ButtonRectFor(ARROW2));
1226 	}
1227 }
1228 
1229 // control_scrollbar
1230 status_t
1231 control_scrollbar(scroll_bar_info *info, BScrollBar *bar)
1232 {
1233 	if (!bar || !info)
1234 		return B_BAD_VALUE;
1235 
1236 	if (bar->fPrivateData->fScrollBarInfo.double_arrows != info->double_arrows) {
1237 		bar->fPrivateData->fScrollBarInfo.double_arrows = info->double_arrows;
1238 
1239 		int8 multiplier = (info->double_arrows) ? 1 : -1;
1240 
1241 		if (bar->fOrientation == B_VERTICAL)
1242 			bar->fPrivateData->fThumbFrame.OffsetBy(0, multiplier * B_H_SCROLL_BAR_HEIGHT);
1243 		else
1244 			bar->fPrivateData->fThumbFrame.OffsetBy(multiplier * B_V_SCROLL_BAR_WIDTH, 0);
1245 	}
1246 
1247 	bar->fPrivateData->fScrollBarInfo.proportional = info->proportional;
1248 
1249 	// TODO: Figure out how proportional relates to the size of the thumb
1250 
1251 	// TODO: Add redraw code to reflect the changes
1252 
1253 	if (info->knob >= 0 && info->knob <= 2)
1254 		bar->fPrivateData->fScrollBarInfo.knob = info->knob;
1255 	else
1256 		return B_BAD_VALUE;
1257 
1258 	if (info->min_knob_size >= SCROLL_BAR_MINIMUM_KNOB_SIZE && info->min_knob_size <= SCROLL_BAR_MAXIMUM_KNOB_SIZE)
1259 		bar->fPrivateData->fScrollBarInfo.min_knob_size = info->min_knob_size;
1260 	else
1261 		return B_BAD_VALUE;
1262 
1263 	return B_OK;
1264 }
1265 
1266 // _DrawDisabledBackground
1267 void
1268 BScrollBar::_DrawDisabledBackground(BRect area,
1269 									const rgb_color& light, const rgb_color& dark,
1270 									const rgb_color& fill)
1271 {
1272 	if (!area.IsValid())
1273 		return;
1274 
1275 	if (fOrientation == B_VERTICAL) {
1276 		int32 height = area.IntegerHeight();
1277 		if (height == 0) {
1278 			SetHighColor(dark);
1279 			StrokeLine(area.LeftTop(), area.RightTop());
1280 		} else if (height == 1) {
1281 			SetHighColor(dark);
1282 			FillRect(area);
1283 		} else {
1284 			BeginLineArray(4);
1285 				AddLine(BPoint(area.left, area.top),
1286 						BPoint(area.right, area.top), dark);
1287 				AddLine(BPoint(area.left, area.bottom - 1),
1288 						BPoint(area.left, area.top + 1), light);
1289 				AddLine(BPoint(area.left + 1, area.top + 1),
1290 						BPoint(area.right, area.top + 1), light);
1291 				AddLine(BPoint(area.right, area.bottom),
1292 						BPoint(area.left, area.bottom), dark);
1293 			EndLineArray();
1294 			area.left++;
1295 			area.top += 2;
1296 			area.bottom--;
1297 			if (area.IsValid()) {
1298 				SetHighColor(fill);
1299 				FillRect(area);
1300 			}
1301 		}
1302 	} else {
1303 		int32 width = area.IntegerWidth();
1304 		if (width == 0) {
1305 			SetHighColor(dark);
1306 			StrokeLine(area.LeftBottom(), area.LeftTop());
1307 		} else if (width == 1) {
1308 			SetHighColor(dark);
1309 			FillRect(area);
1310 		} else {
1311 			BeginLineArray(4);
1312 				AddLine(BPoint(area.left, area.bottom),
1313 						BPoint(area.left, area.top), dark);
1314 				AddLine(BPoint(area.left + 1, area.bottom),
1315 						BPoint(area.left + 1, area.top + 1), light);
1316 				AddLine(BPoint(area.left + 1, area.top),
1317 						BPoint(area.right - 1, area.top), light);
1318 				AddLine(BPoint(area.right, area.top),
1319 						BPoint(area.right, area.bottom), dark);
1320 			EndLineArray();
1321 			area.left += 2;
1322 			area.top ++;
1323 			area.right--;
1324 			if (area.IsValid()) {
1325 				SetHighColor(fill);
1326 				FillRect(area);
1327 			}
1328 		}
1329 	}
1330 }
1331 
1332 // _DrawArrowButton
1333 void
1334 BScrollBar::_DrawArrowButton(int32 direction, bool doubleArrows, BRect r,
1335 							 const BRect& updateRect, bool enabled, bool down)
1336 {
1337 	if (!updateRect.Intersects(r))
1338 		return;
1339 
1340 	rgb_color c = ui_color(B_PANEL_BACKGROUND_COLOR);
1341 	rgb_color light, dark, darker, normal, arrow;
1342 
1343 	if (down && fPrivateData->fDoRepeat) {
1344 		light = tint_color(c, (B_DARKEN_1_TINT + B_DARKEN_2_TINT) / 2.0);
1345 		dark = darker = c;
1346 		normal = tint_color(c, B_DARKEN_1_TINT);
1347 		arrow = tint_color(c, B_DARKEN_MAX_TINT);
1348 
1349 	} else {
1350 		// Add a usability perk - disable buttons if they would not do anything -
1351 		// like a left arrow if the value==fMin
1352 // NOTE: disabled because of too much visual noise/distraction
1353 /*		if ((direction == ARROW_LEFT || direction == ARROW_UP) && (fValue == fMin) )
1354 			use_enabled_colors = false;
1355 		else if ((direction == ARROW_RIGHT || direction == ARROW_DOWN) && (fValue == fMax) )
1356 			use_enabled_colors = false;*/
1357 
1358 		if (enabled) {
1359 			light = tint_color(c, B_LIGHTEN_MAX_TINT);
1360 			dark = tint_color(c, B_DARKEN_1_TINT);
1361 			darker = tint_color(c, B_DARKEN_2_TINT);
1362 			normal = c;
1363 			arrow = tint_color(c, (B_DARKEN_MAX_TINT + B_DARKEN_4_TINT) / 2.0);
1364 		} else {
1365 			light = tint_color(c, B_LIGHTEN_MAX_TINT);
1366 			dark = tint_color(c, B_LIGHTEN_1_TINT);
1367 			darker = tint_color(c, B_DARKEN_2_TINT);
1368 			normal = tint_color(c, B_LIGHTEN_2_TINT);
1369 			arrow = tint_color(c, B_DARKEN_1_TINT);
1370 		}
1371 	}
1372 
1373 	BPoint tri1, tri2, tri3;
1374 	r.InsetBy(4, 4);
1375 
1376 	switch (direction) {
1377 		case ARROW_LEFT:
1378 			tri1.Set(r.right, r.top);
1379 			tri2.Set(r.left + 1, (r.top + r.bottom + 1) /2 );
1380 			tri3.Set(r.right, r.bottom + 1);
1381 			break;
1382 		case ARROW_RIGHT:
1383 			tri1.Set(r.left, r.bottom + 1);
1384 			tri2.Set(r.right - 1, (r.top + r.bottom + 1) / 2);
1385 			tri3.Set(r.left, r.top);
1386 			break;
1387 		case ARROW_UP:
1388 			tri1.Set(r.left, r.bottom);
1389 			tri2.Set((r.left + r.right + 1) / 2, r.top + 1);
1390 			tri3.Set(r.right + 1, r.bottom);
1391 			break;
1392 		default:
1393 			tri1.Set(r.left, r.top);
1394 			tri2.Set((r.left + r.right + 1) / 2, r.bottom - 1);
1395 			tri3.Set(r.right + 1, r.top);
1396 			break;
1397 	}
1398 	// offset triangle if down
1399 	if (down && fPrivateData->fDoRepeat) {
1400 		BPoint offset(1.0, 1.0);
1401 		tri1 = tri1 + offset;
1402 		tri2 = tri2 + offset;
1403 		tri3 = tri3 + offset;
1404 	}
1405 
1406 	r.InsetBy(-3, -3);
1407 	SetHighColor(normal);
1408 	FillRect(r);
1409 
1410 	BShape arrowShape;
1411 	arrowShape.MoveTo(tri1);
1412 	arrowShape.LineTo(tri2);
1413 	arrowShape.LineTo(tri3);
1414 
1415 	SetHighColor(arrow);
1416 	SetPenSize(2.0);
1417 	StrokeShape(&arrowShape);
1418 	SetPenSize(1.0);
1419 
1420 	r.InsetBy(-1, -1);
1421 	BeginLineArray(4);
1422 	if (direction == ARROW_LEFT || direction == ARROW_RIGHT) {
1423 		// horizontal
1424 		if (doubleArrows && direction == ARROW_LEFT) {
1425 			// draw in such a way that the arrows are
1426 			// more visually separated
1427 			AddLine(BPoint(r.left + 1, r.top),
1428 					BPoint(r.right - 1, r.top), light);
1429 			AddLine(BPoint(r.right, r.top),
1430 					BPoint(r.right, r.bottom), darker);
1431 		} else {
1432 			AddLine(BPoint(r.left + 1, r.top),
1433 					BPoint(r.right, r.top), light);
1434 			AddLine(BPoint(r.right, r.top + 1),
1435 					BPoint(r.right, r.bottom), dark);
1436 		}
1437 		AddLine(BPoint(r.left, r.bottom),
1438 				BPoint(r.left, r.top), light);
1439 		AddLine(BPoint(r.right - 1, r.bottom),
1440 				BPoint(r.left + 1, r.bottom), dark);
1441 	} else {
1442 		// vertical
1443 		if (doubleArrows && direction == ARROW_UP) {
1444 			// draw in such a way that the arrows are
1445 			// more visually separated
1446 			AddLine(BPoint(r.left, r.bottom - 1),
1447 					BPoint(r.left, r.top), light);
1448 			AddLine(BPoint(r.right, r.bottom),
1449 					BPoint(r.left, r.bottom), darker);
1450 		} else {
1451 			AddLine(BPoint(r.left, r.bottom),
1452 					BPoint(r.left, r.top), light);
1453 			AddLine(BPoint(r.right, r.bottom),
1454 					BPoint(r.left + 1, r.bottom), dark);
1455 		}
1456 		AddLine(BPoint(r.left + 1, r.top),
1457 				BPoint(r.right, r.top), light);
1458 		AddLine(BPoint(r.right, r.top + 1),
1459 				BPoint(r.right, r.bottom - 1), dark);
1460 	}
1461 	EndLineArray();
1462 }
1463 
1464