1/*
2 * Copyright 2001 Werner Freytag - please read to the LICENSE file
3 *
4 * Copyright 2002-2015, Stephan Aßmus <superstippi@gmx.de>
5 * All rights reserved.
6 *
7 */
8
9#include "ColorSlider.h"
10
11#include <stdio.h>
12
13#include <Bitmap.h>
14#include <ControlLook.h>
15#include <LayoutUtils.h>
16#include <OS.h>
17#include <Window.h>
18#include <math.h>
19
20#include "support_ui.h"
21
22#include "rgb_hsv.h"
23
24
25#define round(x) (int)(x+.5)
26
27enum {
28	MSG_UPDATE			= 'Updt',
29};
30
31#define MAX_X 255
32#define MAX_Y 255
33
34
35ColorSlider::ColorSlider(SelectedColorMode mode,
36	float value1, float value2, orientation dir, border_style border)
37	:  BControl("ColorSlider", "", new BMessage(MSG_COLOR_SLIDER),
38		B_WILL_DRAW | B_FRAME_EVENTS)
39{
40	_Init(mode, value1, value2, dir, border);
41	FrameResized(Bounds().Width(), Bounds().Height());
42}
43
44
45ColorSlider::ColorSlider(BPoint offsetPoint, SelectedColorMode mode,
46	float value1, float value2, orientation dir, border_style border)
47	:  BControl(BRect(0.0, 0.0, 35.0, 265.0).OffsetToCopy(offsetPoint),
48		"ColorSlider", "", new BMessage(MSG_COLOR_SLIDER),
49		B_FOLLOW_LEFT | B_FOLLOW_TOP, B_WILL_DRAW | B_FRAME_EVENTS)
50{
51	_Init(mode, value1, value2, dir, border);
52	FrameResized(Bounds().Width(), Bounds().Height());
53}
54
55
56ColorSlider::~ColorSlider()
57{
58	delete fBitmap;
59}
60
61
62BSize
63ColorSlider::MinSize()
64{
65	BSize minSize;
66	if (fOrientation == B_VERTICAL)
67		minSize = BSize(36, 10 + MAX_Y / 17);
68	else
69		minSize = BSize(10 + MAX_X / 17, 10);
70	return BLayoutUtils::ComposeSize(ExplicitMinSize(), minSize);
71}
72
73
74BSize
75ColorSlider::PreferredSize()
76{
77	return BLayoutUtils::ComposeSize(ExplicitPreferredSize(), MinSize());
78}
79
80
81BSize
82ColorSlider::MaxSize()
83{
84	BSize maxSize;
85	if (fOrientation == B_VERTICAL)
86		maxSize = BSize(36, 10 + MAX_Y);
87	else
88		maxSize = BSize(10 + MAX_X, 18);
89	return BLayoutUtils::ComposeSize(ExplicitMaxSize(), maxSize);
90}
91
92
93void
94ColorSlider::AttachedToWindow()
95{
96}
97
98
99status_t
100ColorSlider::Invoke(BMessage* message)
101{
102	if (message == NULL)
103		message = Message();
104
105	if (message == NULL)
106		return BControl::Invoke(message);
107
108	message->RemoveName("value");
109	message->RemoveName("begin");
110
111	switch (fMode) {
112		case R_SELECTED:
113		case G_SELECTED:
114		case B_SELECTED:
115			message->AddFloat("value", 1.0 - (float)Value() / 255);
116			break;
117
118		case H_SELECTED:
119			message->AddFloat("value", (1.0 - (float)Value() / 255) * 6);
120			break;
121
122		case S_SELECTED:
123		case V_SELECTED:
124			message->AddFloat("value", 1.0 - (float)Value() / 255);
125			break;
126	}
127
128	// some other parts of WonderBrush rely on this.
129	// if the flag is present, it triggers generating an undo action
130	// fMouseDown is not set yet the first message is sent
131	if (!fMouseDown)
132		message->AddBool("begin", true);
133
134	return BControl::Invoke(message);
135}
136
137
138void
139ColorSlider::Draw(BRect updateRect)
140{
141	if (fBitmapDirty && fBitmap != NULL) {
142		_FillBitmap(fBitmap, fMode, fFixedValue1, fFixedValue2, fOrientation);
143		fBitmapDirty = false;
144	}
145
146	BRect bounds = _BitmapRect();
147
148	// Frame
149	if (fBorderStyle == B_FANCY_BORDER) {
150		bounds.InsetBy(-2, -2);
151		rgb_color color = LowColor();
152		be_control_look->DrawTextControlBorder(this, bounds, updateRect,
153			color);
154	}
155
156	// Color slider fill
157	if (fBitmap != NULL)
158		DrawBitmap(fBitmap, bounds.LeftTop());
159	else {
160		SetHighColor(255, 0, 0);
161		FillRect(bounds);
162	}
163
164	// Marker background
165	if (fOrientation == B_VERTICAL) {
166		bounds.InsetBy(-2, -2);
167		BRect r = Bounds();
168		FillRect(BRect(r.left, r.top, bounds.left - 1, r.bottom),
169			B_SOLID_LOW);
170		FillRect(BRect(bounds.right + 1, r.top, r.right, r.bottom),
171			B_SOLID_LOW);
172		FillRect(
173			BRect(bounds.left, r.top, bounds.right, bounds.top - 1),
174			B_SOLID_LOW);
175		FillRect(
176			BRect(bounds.left, bounds.bottom + 1, bounds.right, r.bottom),
177			B_SOLID_LOW);
178	}
179
180	// marker
181	if (fOrientation == B_VERTICAL) {
182		// draw the triangle markers
183		SetHighColor(0, 0, 0);
184		BRect r = Bounds();
185		float offset = Value() * (r.Height() - 10) / 255.0;
186		_DrawTriangle(
187			BPoint(r.left, offset),
188			BPoint(r.left + 5.0, offset + 5.0),
189			BPoint(r.left, offset + 10.0));
190
191		_DrawTriangle(
192			BPoint(r.right, offset),
193			BPoint(r.right - 5.0, offset + 5.0),
194			BPoint(r.right, offset + 10.0));
195	} else {
196		BRect r = bounds;
197		float x = bounds.left - 2 + (255 - Value()) * bounds.Width() / 255.0;
198		if (x > r.left) {
199			SetHighColor(255, 255, 255);
200			StrokeLine(BPoint(x, bounds.top),
201				BPoint(x, bounds.bottom));
202		}
203		if (x + 1 > r.left) {
204			SetHighColor(0, 0, 0);
205			StrokeLine(BPoint(x + 1, bounds.top),
206				BPoint(x + 1, bounds.bottom));
207		}
208		if (x + 3 < r.right) {
209			SetHighColor(0, 0, 0);
210			StrokeLine(BPoint(x + 3, bounds.top),
211				BPoint(x + 3, bounds.bottom));
212		}
213		if (x + 4 < r.right) {
214			SetHighColor(255, 255, 255);
215			StrokeLine(BPoint(x + 4, bounds.top),
216				BPoint(x + 4, bounds.bottom));
217		}
218	}
219}
220
221
222void
223ColorSlider::FrameResized(float width, float height)
224{
225	BRect r = _BitmapRect();
226	_AllocBitmap(r.IntegerWidth() + 1, r.IntegerHeight() + 1);
227	Invalidate();
228}
229
230
231void
232ColorSlider::MouseDown(BPoint where)
233{
234	SetMouseEventMask(B_POINTER_EVENTS,
235		B_SUSPEND_VIEW_FOCUS | B_LOCK_WINDOW_FOCUS);
236	_TrackMouse(where);
237	fMouseDown = true;
238}
239
240
241void
242ColorSlider::MouseUp(BPoint where)
243{
244	fMouseDown = false;
245}
246
247
248void
249ColorSlider::MouseMoved( BPoint where, uint32 code,
250	const BMessage* dragMessage)
251{
252	if (dragMessage != NULL || !fMouseDown)
253		return;
254
255	_TrackMouse(where);
256}
257
258
259void
260ColorSlider::SetValue(int32 value)
261{
262	value = max_c(min_c(value, 255), 0);
263	if (value != Value()) {
264		BControl::SetValue(value);
265		Invalidate();
266	}
267}
268
269
270void
271ColorSlider::SetModeAndValues(SelectedColorMode mode,
272							  float value1, float value2)
273{
274	float R = 0;
275	float G = 0;
276	float B = 0;
277	float h = 0;
278	float s = 0;
279	float v = 0;
280
281	switch (fMode) {
282		case R_SELECTED:
283			R = 255 - Value();
284			G = round(fFixedValue1 * 255.0);
285			B = round(fFixedValue2 * 255.0);
286			break;
287
288		case G_SELECTED:
289			R = round(fFixedValue1 * 255.0);
290			G = 255 - Value();
291			B = round(fFixedValue2 * 255.0);
292			break;
293
294		case B_SELECTED:
295			R = round(fFixedValue1 * 255.0);
296			G = round(fFixedValue2 * 255.0);
297			B = 255 - Value();
298			break;
299
300		case H_SELECTED:
301			h = (1.0 - (float)Value()/255.0)*6.0;
302			s = fFixedValue1;
303			v = fFixedValue2;
304			break;
305
306		case S_SELECTED:
307			h = fFixedValue1;
308			s = 1.0 - (float)Value()/255.0;
309			v = fFixedValue2;
310			break;
311
312		case V_SELECTED:
313			h = fFixedValue1;
314			s = fFixedValue2;
315			v = 1.0 - (float)Value()/255.0;
316			break;
317	}
318
319	if ((fMode & (H_SELECTED | S_SELECTED | V_SELECTED)) != 0) {
320		HSV_to_RGB(h, s, v, R, G, B);
321		R *= 255.0;
322		G *= 255.0;
323		B *= 255.0;
324	}
325
326	rgb_color color = { (uint8)round(R), (uint8)round(G), (uint8)round(B),
327		255 };
328
329	fMode = mode;
330	SetOtherValues(value1, value2);
331
332	SetMarkerToColor(color);
333	_Update();
334}
335
336
337void
338ColorSlider::SetOtherValues(float value1, float value2)
339{
340	fFixedValue1 = value1;
341	fFixedValue2 = value2;
342
343	if (fMode != H_SELECTED)
344		_Update();
345}
346
347
348void
349ColorSlider::GetOtherValues(float* value1, float* value2) const
350{
351	if (value1 != NULL)
352		*value1 = fFixedValue1;
353
354	if (value2 != NULL)
355		*value2 = fFixedValue2;
356}
357
358
359void
360ColorSlider::SetMarkerToColor(rgb_color color)
361{
362	float h = 0;
363	float s = 0;
364	float v = 0;
365	if ((fMode & (H_SELECTED | S_SELECTED | V_SELECTED)) != 0) {
366		RGB_to_HSV(
367			(float)color.red / 255.0,
368			(float)color.green / 255.0,
369			(float)color.blue / 255.0,
370			h, s, v
371		);
372	}
373
374	switch (fMode) {
375		case R_SELECTED:
376			SetValue(255 - color.red);
377			break;
378
379		case G_SELECTED:
380			SetValue(255 - color.green);
381			break;
382
383		case B_SELECTED:
384			SetValue(255 - color.blue);
385			break;
386
387		case H_SELECTED:
388			SetValue(255.0 - round(h / 6.0 * 255.0));
389			break;
390
391		case S_SELECTED:
392			SetValue(255.0 - round(s * 255.0));
393			break;
394
395		case V_SELECTED:
396			SetValue(255.0 - round(v * 255.0));
397			break;
398	}
399}
400
401
402// #pragma mark - private
403
404
405void
406ColorSlider::_Init(SelectedColorMode mode, float value1, float value2,
407	orientation dir, border_style border)
408{
409	fMode = mode;
410	fFixedValue1 = value1;
411	fFixedValue2 = value2;
412	fMouseDown = false;
413	fBitmap = NULL;
414	fBitmapDirty = true;
415	fOrientation = dir;
416	fBorderStyle = border;
417
418	SetViewColor(B_TRANSPARENT_COLOR);
419	SetLowColor(ui_color(B_PANEL_BACKGROUND_COLOR));
420}
421
422
423void
424ColorSlider::_AllocBitmap(int32 width, int32 height)
425{
426	if (width < 2 || height < 2)
427		return;
428
429	delete fBitmap;
430	fBitmap = new BBitmap(BRect(0, 0, width - 1, height - 1), 0, B_RGB32);
431
432	fBitmapDirty = true;
433}
434
435
436void
437ColorSlider::_Update()
438{
439	fBitmapDirty = true;
440	Invalidate();
441}
442
443
444BRect
445ColorSlider::_BitmapRect() const
446{
447	BRect r = Bounds();
448	if (fOrientation == B_VERTICAL)
449		r.InsetBy(7, 3);
450	if (fBorderStyle == B_FANCY_BORDER)
451		r.InsetBy(2, 2);
452	return r;
453}
454
455
456void
457ColorSlider::_FillBitmap(BBitmap* bitmap, SelectedColorMode mode,
458	float fixedValue1, float fixedValue2, orientation orient) const
459{
460	int32 width = bitmap->Bounds().IntegerWidth();
461	int32 height = bitmap->Bounds().IntegerHeight();
462	uint32 bpr = bitmap->BytesPerRow();
463
464	uint8* bits = (uint8*)bitmap->Bits();
465
466	float r = 0;
467	float g = 0;
468	float b = 0;
469	float h;
470	float s;
471	float v;
472
473	switch (mode) {
474		case R_SELECTED:
475			g = round(fixedValue1 * 255);
476			b = round(fixedValue2 * 255);
477			if (orient == B_VERTICAL) {
478				for (int y = 0; y <= height; y++) {
479					r = 255 - y * 255 / height;
480					_DrawColorLineY(bits, width, r, g, b);
481					bits += bpr;
482				}
483			} else {
484				for (int x = 0; x <= width; x++) {
485					r = x * 255 / width;
486					_DrawColorLineX(bits, height, bpr, r, g, b);
487					bits += 4;
488				}
489			}
490			break;
491
492		case G_SELECTED:
493			r = round(fixedValue1 * 255);
494			b = round(fixedValue2 * 255);
495			if (orient == B_VERTICAL) {
496				for (int y = 0; y <= height; y++) {
497					g = 255 - y * 255 / height;
498					_DrawColorLineY(bits, width, r, g, b);
499					bits += bpr;
500				}
501			} else {
502				for (int x = 0; x <= width; x++) {
503					g = 255 - x * 255 / width;
504					_DrawColorLineX(bits, height, bpr, r, g, b);
505					bits += 4;
506				}
507			}
508			break;
509
510		case B_SELECTED:
511			r = round(fixedValue1 * 255);
512			g = round(fixedValue2 * 255);
513			if (orient == B_VERTICAL) {
514				for (int y = 0; y <= height; y++) {
515					b = 255 - y * 255 / height;
516					_DrawColorLineY(bits, width, r, g, b);
517					bits += bpr;
518				}
519			} else {
520				for (int x = 0; x <= width; x++) {
521					b = x * 255 / width;
522					_DrawColorLineX(bits, height, bpr, r, g, b);
523					bits += 4;
524				}
525			}
526
527		case H_SELECTED:
528			s = 1.0;//fixedValue1;
529			v = 1.0;//fixedValue2;
530			if (orient == B_VERTICAL) {
531				for (int y = 0; y <= height; y++) {
532					HSV_to_RGB(6.0 - (float)y * 6.0 / height, s, v, r, g, b);
533					_DrawColorLineY(bits, width, r * 255, g * 255, b * 255);
534					bits += bpr;
535				}
536			} else {
537				for (int x = 0; x <= width; x++) {
538					HSV_to_RGB((float)x * 6.0 / width, s, v, r, g, b);
539					_DrawColorLineX(bits, height, bpr,
540						r * 255, g * 255, b * 255);
541					bits += 4;
542				}
543			}
544			break;
545
546		case S_SELECTED:
547			h = fixedValue1;
548			v = 1.0;//fixedValue2;
549			if (orient == B_VERTICAL) {
550				for (int y = 0; y <= height; y++) {
551					HSV_to_RGB(h, 1.0 - (float)y / height, v, r, g, b);
552					_DrawColorLineY(bits, width, r * 255, g * 255, b * 255);
553					bits += bpr;
554				}
555			} else {
556				for (int x = 0; x <= width; x++) {
557					HSV_to_RGB(h, 1.0 - (float)x / width, v, r, g, b);
558					_DrawColorLineX(bits, height, bpr,
559						r * 255, g * 255, b * 255);
560					bits += 4;
561				}
562			}
563			break;
564
565		case V_SELECTED:
566			h = fixedValue1;
567			s = 1.0;//fixedValue2;
568			if (orient == B_VERTICAL) {
569				for (int y = 0; y <= height; y++) {
570					HSV_to_RGB(h, s, 1.0 - (float)y / height, r, g, b);
571					_DrawColorLineY(bits, width, r * 255, g * 255, b * 255);
572					bits += bpr;
573				}
574			} else {
575				for (int x = 0; x <= width; x++) {
576					HSV_to_RGB(h, s, (float)x / width, r, g, b);
577					_DrawColorLineX(bits, height, bpr,
578						r * 255, g * 255, b * 255);
579					bits += 4;
580				}
581			}
582			break;
583	}
584}
585
586
587void
588ColorSlider::_DrawColorLineY(uint8* bits, int width, int r, int g, int b)
589{
590	for (int x = 0; x <= width; x++) {
591		bits[0] = b;
592		bits[1] = g;
593		bits[2] = r;
594		bits[3] = 255;
595		bits += 4;
596	}
597}
598
599
600void
601ColorSlider::_DrawColorLineX(uint8* bits, int height, int bpr,
602	int r, int g, int b)
603{
604	for (int y = 0; y <= height; y++) {
605		bits[0] = b;
606		bits[1] = g;
607		bits[2] = r;
608		bits[3] = 255;
609		bits += bpr;
610	}
611}
612
613
614void
615ColorSlider::_DrawTriangle(BPoint point1, BPoint point2, BPoint point3)
616{
617	StrokeLine(point1, point2);
618	StrokeLine(point3);
619	StrokeLine(point1);
620}
621
622
623void
624ColorSlider::_TrackMouse(BPoint where)
625{
626	BRect b(_BitmapRect());
627
628	if (fOrientation == B_VERTICAL)
629		SetValue((int)(((where.y - b.top) / b.Height()) * 255.0));
630	else
631		SetValue(255 - (int)(((where.x - b.left) / b.Width()) * 255.0));
632
633	Invoke();
634}
635