1/*
2 * Copyright 2006-2012 Stephan Aßmus <superstippi@gmx.de>
3 * Distributed under the terms of the MIT License.
4 */
5
6#include "AlphaSlider.h"
7
8#include <stdio.h>
9#include <string.h>
10
11#include <AppDefs.h>
12#include <Bitmap.h>
13#include <ControlLook.h>
14#include <LayoutUtils.h>
15#include <Message.h>
16#include <Window.h>
17
18#include "ui_defines.h"
19
20// constructor
21AlphaSlider::AlphaSlider(orientation dir, BMessage* message,
22	border_style border)
23	: BControl("alpha slider", NULL, message,
24		B_WILL_DRAW | B_FRAME_EVENTS | B_NAVIGABLE)
25	, fBitmap(NULL)
26	, fColor(kBlack)
27	, fDragging(false)
28	, fOrientation(dir)
29	, fBorderStyle(border)
30{
31	FrameResized(Bounds().Width(), Bounds().Height());
32
33	SetViewColor(B_TRANSPARENT_COLOR);
34	SetLowColor(ui_color(B_PANEL_BACKGROUND_COLOR));
35
36	SetValue(255);
37}
38
39// destructor
40AlphaSlider::~AlphaSlider()
41{
42	delete fBitmap;
43}
44
45// WindowActivated
46void
47AlphaSlider::WindowActivated(bool active)
48{
49	if (IsFocus())
50		Invalidate();
51}
52
53// MakeFocus
54void
55AlphaSlider::MakeFocus(bool focus)
56{
57	if (focus != IsFocus()) {
58		BControl::MakeFocus(focus);
59		Invalidate();
60	}
61}
62
63// MinSize
64BSize
65AlphaSlider::MinSize()
66{
67	BSize minSize;
68	if (fOrientation == B_HORIZONTAL)
69		minSize = BSize(255 + 4, 7 + 4);
70	else
71		minSize = BSize(7 + 4, 255 + 4);
72	return BLayoutUtils::ComposeSize(ExplicitMinSize(), minSize);
73}
74
75// PreferredSize
76BSize
77AlphaSlider::PreferredSize()
78{
79	return BLayoutUtils::ComposeSize(ExplicitPreferredSize(), MinSize());
80}
81
82// MaxSize
83BSize
84AlphaSlider::MaxSize()
85{
86	BSize minSize;
87	if (fOrientation == B_HORIZONTAL)
88		minSize = BSize(B_SIZE_UNLIMITED, 16 + 4);
89	else
90		minSize = BSize(16 + 4, B_SIZE_UNLIMITED);
91	return BLayoutUtils::ComposeSize(ExplicitMinSize(), minSize);
92}
93
94// MouseDown
95void
96AlphaSlider::MouseDown(BPoint where)
97{
98	if (!IsEnabled())
99		return;
100
101//	if (!IsFocus())
102//		MakeFocus(true);
103
104	fDragging = true;
105	SetValue(_ValueFor(where));
106
107	SetMouseEventMask(B_POINTER_EVENTS, B_LOCK_WINDOW_FOCUS);
108}
109
110// MouseUp
111void
112AlphaSlider::MouseUp(BPoint where)
113{
114	fDragging = false;
115}
116
117// MouseMoved
118void
119AlphaSlider::MouseMoved(BPoint where, uint32 transit,
120	const BMessage* dragMessage)
121{
122	if (!IsEnabled() || !fDragging)
123		return;
124
125	SetValue(_ValueFor(where));
126}
127
128// KeyDown
129void
130AlphaSlider::KeyDown(const char* bytes, int32 numBytes)
131{
132	if (!IsEnabled() || numBytes <= 0) {
133		BControl::KeyDown(bytes, numBytes);
134		return;
135	}
136
137	switch (bytes[0]) {
138		case B_HOME:
139			SetValue(0);
140			break;
141		case B_END:
142			SetValue(255);
143			break;
144
145		case B_LEFT_ARROW:
146		case B_UP_ARROW:
147			SetValue(max_c(0, Value() - 1));
148			break;
149		case B_RIGHT_ARROW:
150		case B_DOWN_ARROW:
151			SetValue(min_c(255, Value() + 1));
152			break;
153
154		default:
155			BControl::KeyDown(bytes, numBytes);
156			break;
157	}
158}
159
160// Draw
161void
162AlphaSlider::Draw(BRect updateRect)
163{
164	BRect b = Bounds();
165	bool isFocus = IsFocus() && Window()->IsActive();
166
167	if (fBorderStyle == B_FANCY_BORDER) {
168		rgb_color bg = LowColor();
169		uint32 flags = 0;
170
171		if (!IsEnabled())
172			flags |= BControlLook::B_DISABLED;
173		if (isFocus)
174			flags |= BControlLook::B_FOCUSED;
175
176		be_control_look->DrawTextControlBorder(this, b, updateRect, bg, flags);
177	}
178
179	DrawBitmap(fBitmap, b.LeftTop());
180
181	// value marker
182	if (fOrientation == B_HORIZONTAL) {
183		float pos = floorf(b.left + Value() * b.Width() / 255.0 + 0.5);
184
185		if (pos - 2 >= b.left) {
186			SetHighColor(kWhite);
187			StrokeLine(BPoint(pos - 2, b.top), BPoint(pos - 2, b.bottom));
188		}
189		if (pos - 1 >= b.left) {
190			SetHighColor(kBlack);
191			StrokeLine(BPoint(pos - 1, b.top), BPoint(pos - 1, b.bottom));
192		}
193		if (pos + 1 <= b.right) {
194			SetHighColor(kBlack);
195			StrokeLine(BPoint(pos + 1, b.top), BPoint(pos + 1, b.bottom));
196		}
197		if (pos + 2 <= b.right) {
198			SetHighColor(kWhite);
199			StrokeLine(BPoint(pos + 2, b.top), BPoint(pos + 2, b.bottom));
200		}
201	} else {
202		float pos = floorf(b.top + Value() * b.Height() / 255.0 + 0.5);
203
204		if (pos - 2 >= b.top) {
205			SetHighColor(kWhite);
206			StrokeLine(BPoint(b.left, pos - 2), BPoint(b.right, pos - 2));
207		}
208		if (pos - 1 >= b.top) {
209			SetHighColor(kBlack);
210			StrokeLine(BPoint(b.left, pos - 1), BPoint(b.right, pos - 1));
211		}
212		if (pos + 1 <= b.bottom) {
213			SetHighColor(kBlack);
214			StrokeLine(BPoint(b.left, pos + 1), BPoint(b.right, pos + 1));
215		}
216		if (pos + 2 <= b.bottom) {
217			SetHighColor(kWhite);
218			StrokeLine(BPoint(b.left, pos + 2), BPoint(b.right, pos + 2));
219		}
220	}
221}
222
223// FrameResized
224void
225AlphaSlider::FrameResized(float width, float height)
226{
227	BRect r = _BitmapRect();
228	_AllocBitmap(r.IntegerWidth() + 1, r.IntegerHeight() + 1);
229	_UpdateColors();
230	Invalidate();
231
232}
233
234// SetValue
235void
236AlphaSlider::SetValue(int32 value)
237{
238	if (value == Value())
239		return;
240
241	Invoke(Message());
242	BControl::SetValue(value);
243}
244
245// SetEnabled
246void
247AlphaSlider::SetEnabled(bool enabled)
248{
249	if (enabled == IsEnabled())
250		return;
251
252	BControl::SetEnabled(enabled);
253
254	_UpdateColors();
255	Invalidate();
256}
257
258// #pragma mark -
259
260// SetColor
261void
262AlphaSlider::SetColor(const rgb_color& color)
263{
264	if ((uint32&)fColor == (uint32&)color)
265		return;
266
267	fColor = color;
268
269	_UpdateColors();
270	Invalidate();
271}
272
273// #pragma mark -
274
275// blend_colors
276inline void
277blend_colors(uint8* d, uint8 alpha, uint8 c1, uint8 c2, uint8 c3)
278{
279	if (alpha > 0) {
280		if (alpha == 255) {
281			d[0] = c1;
282			d[1] = c2;
283			d[2] = c3;
284		} else {
285			d[0] += (uint8)(((c1 - d[0]) * alpha) >> 8);
286			d[1] += (uint8)(((c2 - d[1]) * alpha) >> 8);
287			d[2] += (uint8)(((c3 - d[2]) * alpha) >> 8);
288		}
289	}
290}
291
292// _UpdateColors
293void
294AlphaSlider::_UpdateColors()
295{
296	if (fBitmap == NULL || !fBitmap->IsValid())
297		return;
298
299	// fill in top row with alpha gradient
300	uint8* topRow = (uint8*)fBitmap->Bits();
301	uint32 width = fBitmap->Bounds().IntegerWidth() + 1;
302
303	uint8* p = topRow;
304	rgb_color color = fColor;
305	if (!IsEnabled()) {
306		// blend low color and color to give disabled look
307		rgb_color bg = LowColor();
308		color.red = (uint8)(((uint32)bg.red + color.red) / 2);
309		color.green = (uint8)(((uint32)bg.green + color.green) / 2);
310		color.blue = (uint8)(((uint32)bg.blue + color.blue) / 2);
311	}
312	for (uint32 x = 0; x < width; x++) {
313		p[0] = color.blue;
314		p[1] = color.green;
315		p[2] = color.red;
316		p[3] = x * 255 / width;
317		p += 4;
318	}
319	// copy top row to rest of bitmap
320	uint32 height = fBitmap->Bounds().IntegerHeight() + 1;
321	uint32 bpr = fBitmap->BytesPerRow();
322	uint8* dstRow = topRow + bpr;
323	for (uint32 i = 1; i < height; i++) {
324		memcpy(dstRow, topRow, bpr);
325		dstRow += bpr;
326	}
327	// post process bitmap to underlay it with a pattern to visualize alpha
328	uint8* row = topRow;
329	for (uint32 i = 0; i < height; i++) {
330		uint8* p = row;
331		for (uint32 x = 0; x < width; x++) {
332			uint8 alpha = p[3];
333			if (alpha < 255) {
334				p[3] = 255;
335				alpha = 255 - alpha;
336				if (x % 8 >= 4) {
337					if (i % 8 >= 4) {
338						blend_colors(p, alpha,
339									 kAlphaLow.blue,
340									 kAlphaLow.green,
341									 kAlphaLow.red);
342					} else {
343						blend_colors(p, alpha,
344									 kAlphaHigh.blue,
345									 kAlphaHigh.green,
346									 kAlphaHigh.red);
347					}
348				} else {
349					if (i % 8 >= 4) {
350						blend_colors(p, alpha,
351									 kAlphaHigh.blue,
352									 kAlphaHigh.green,
353									 kAlphaHigh.red);
354					} else {
355						blend_colors(p, alpha,
356									 kAlphaLow.blue,
357									 kAlphaLow.green,
358									 kAlphaLow.red);
359					}
360				}
361			}
362			p += 4;
363		}
364		row += bpr;
365	}
366}
367
368// _AllocBitmap
369void
370AlphaSlider::_AllocBitmap(int32 width, int32 height)
371{
372	if (width < 2 || height < 2)
373		return;
374
375	delete fBitmap;
376	fBitmap = new BBitmap(BRect(0, 0, width - 1, height - 1), 0, B_RGB32);
377}
378
379// _BitmapRect
380BRect
381AlphaSlider::_BitmapRect() const
382{
383	BRect r = Bounds();
384	if (fBorderStyle == B_FANCY_BORDER)
385		r.InsetBy(2, 2);
386	return r;
387}
388
389// _ValueFor
390int32
391AlphaSlider::_ValueFor(BPoint where) const
392{
393	BRect r = _BitmapRect();
394
395	int32 value;
396	if (fOrientation == B_HORIZONTAL)
397		value = (int32)(255 * (where.x - r.left) / r.Width() + 0.5);
398	else
399		value = (int32)(255 * (where.y - r.top) / r.Height() + 0.5);
400
401	value = max_c(0, value);
402	value = min_c(255, value);
403
404	return value;
405}
406
407