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