1/*
2 * Copyright 2006, Haiku.
3 * Distributed under the terms of the MIT License.
4 *
5 * Authors:
6 *		Stephan Aßmus <superstippi@gmx.de>
7 */
8
9#include "GradientControl.h"
10
11#include <stdio.h>
12
13#include <AppDefs.h>
14#include <Bitmap.h>
15#include <ControlLook.h>
16#include <Message.h>
17#include <Window.h>
18
19#include "ui_defines.h"
20#include "support_ui.h"
21
22#include "GradientTransformable.h"
23
24// constructor
25GradientControl::GradientControl(BMessage* message, BHandler* target)
26	: BView(BRect(0, 0, 259, 19), "gradient control", B_FOLLOW_NONE,
27			B_WILL_DRAW | B_FRAME_EVENTS | B_NAVIGABLE),
28	  fGradient(new ::Gradient()),
29	  fGradientBitmap(NULL),
30	  fDraggingStepIndex(-1),
31	  fCurrentStepIndex(-1),
32	  fDropOffset(-1.0),
33	  fDropIndex(-1),
34	  fEnabled(true),
35	  fMessage(message),
36	  fTarget(target)
37{
38	FrameResized(Bounds().Width(), Bounds().Height());
39	SetViewColor(B_TRANSPARENT_32_BIT);
40	SetLowColor(ui_color(B_PANEL_BACKGROUND_COLOR));
41}
42
43// destructor
44GradientControl::~GradientControl()
45{
46	delete fGradient;
47	delete fGradientBitmap;
48	delete fMessage;
49}
50
51#if LIB_LAYOUT
52// layoutprefs
53minimax
54GradientControl::layoutprefs()
55{
56	mpm.mini.x = 256 + 4;
57	mpm.maxi.x = mpm.mini.x + 10000;
58	mpm.mini.y = 20;
59	mpm.maxi.y = mpm.mini.y + 10;
60
61	mpm.weight = 2.0;
62
63	return mpm;
64}
65
66// layout
67BRect
68GradientControl::layout(BRect frame)
69{
70	MoveTo(frame.LeftTop());
71	ResizeTo(frame.Width(), frame.Height());
72	return Frame();
73}
74#endif // LIB_LAYOUT
75
76// WindowActivated
77void
78GradientControl::WindowActivated(bool active)
79{
80	if (IsFocus())
81		Invalidate();
82}
83
84// MakeFocus
85void
86GradientControl::MakeFocus(bool focus)
87{
88	if (focus != IsFocus()) {
89		_UpdateCurrentColor();
90		Invalidate();
91		if (fTarget) {
92			if (BLooper* looper = fTarget->Looper())
93				looper->PostMessage(MSG_GRADIENT_CONTROL_FOCUS_CHANGED, fTarget);
94		}
95	}
96	BView::MakeFocus(focus);
97}
98
99// MouseDown
100void
101GradientControl::MouseDown(BPoint where)
102{
103	if (!fEnabled)
104		return;
105
106	if (!IsFocus()) {
107		MakeFocus(true);
108	}
109
110	fDraggingStepIndex = _StepIndexFor(where);
111
112	if (fDraggingStepIndex >= 0)
113		SetMouseEventMask(B_POINTER_EVENTS, B_LOCK_WINDOW_FOCUS);
114
115	// handle double click
116	int32 clicks;
117	if (Window()->CurrentMessage()->FindInt32("clicks", &clicks) >= B_OK && clicks >= 2) {
118		if (fDraggingStepIndex < 0) {
119			// create a new offset at the click location that uses
120			// the interpolated color
121			float offset = _OffsetFor(where);
122			// create a clean gradient
123			uint32 width = fGradientBitmap->Bounds().IntegerWidth();
124			uint8* temp = new uint8[width * 4];
125			fGradient->MakeGradient((uint32*)temp, width);
126			// get the color at the offset
127			rgb_color color;
128			uint8* bits = temp;
129			bits += 4 * (uint32)((width - 1) * offset);
130			color.red = bits[0];
131			color.green = bits[1];
132			color.blue = bits[2];
133			color.alpha = bits[3];
134			fCurrentStepIndex = fGradient->AddColor(color, offset);
135			fDraggingStepIndex = -1;
136			_UpdateColors();
137			Invalidate();
138			_UpdateCurrentColor();
139			delete[] temp;
140		}
141	}
142
143	if (fCurrentStepIndex != fDraggingStepIndex && fDraggingStepIndex >= 0) {
144		// start dragging this stop
145		fCurrentStepIndex = fDraggingStepIndex;
146		Invalidate();
147		_UpdateCurrentColor();
148	}
149}
150
151// MouseUp
152void
153GradientControl::MouseUp(BPoint where)
154{
155	fDraggingStepIndex = -1;
156}
157
158// MouseMoved
159void
160GradientControl::MouseMoved(BPoint where, uint32 transit, const BMessage* dragMessage)
161{
162	if (!fEnabled)
163		return;
164
165	float offset = _OffsetFor(where);
166
167	if (fDraggingStepIndex >= 0) {
168		BGradient::ColorStop* step = fGradient->ColorAt(fDraggingStepIndex);
169		if (step) {
170			if (fGradient->SetOffset(fDraggingStepIndex, offset)) {
171				_UpdateColors();
172				Invalidate();
173			}
174		}
175	}
176	int32 dropIndex = -1;
177	float dropOffset = -1.0;
178	if (dragMessage && (transit == B_INSIDE_VIEW || transit == B_ENTERED_VIEW)) {
179		rgb_color dragColor;
180		if (restore_color_from_message(dragMessage, dragColor, 0) >= B_OK) {
181			dropIndex = _StepIndexFor(where);
182			// fall back to inserting a color step if no direct hit on an existing step
183			if (dropIndex < 0)
184				dropOffset = offset;
185		}
186	}
187	if (fDropOffset != dropOffset || fDropIndex != dropIndex) {
188		fDropOffset = dropOffset;
189		fDropIndex = dropIndex;
190		Invalidate();
191	}
192}
193
194// MessageReceived
195void
196GradientControl::MessageReceived(BMessage* message)
197{
198	switch (message->what) {
199		case B_PASTE:
200			if (fEnabled) {
201				rgb_color color;
202				if (restore_color_from_message(message, color, 0) >= B_OK) {
203					bool update = false;
204					if (fDropIndex >= 0) {
205						if (BGradient::ColorStop* step
206								= fGradient->ColorAt(fDropIndex)) {
207							color.alpha = step->color.alpha;
208						}
209						fGradient->SetColor(fDropIndex, color);
210						fCurrentStepIndex = fDropIndex;
211						fDropIndex = -1;
212						update = true;
213					} else if (fDropOffset >= 0.0) {
214						fCurrentStepIndex = fGradient->AddColor(color,
215							fDropOffset);
216						fDropOffset = -1.0;
217						update = true;
218					}
219					if (update) {
220						_UpdateColors();
221						if (!IsFocus())
222							MakeFocus(true);
223						else
224							Invalidate();
225						_UpdateCurrentColor();
226					}
227				}
228			}
229			break;
230		default:
231			BView::MessageReceived(message);
232	}
233}
234
235// KeyDown
236void
237GradientControl::KeyDown(const char* bytes, int32 numBytes)
238{
239	bool handled = false;
240	bool update = false;
241	if (fEnabled) {
242		if (numBytes > 0) {
243			handled = true;
244			int32 count = fGradient->CountColors();
245			switch (bytes[0]) {
246				case B_DELETE:
247					// remove step
248					update = fGradient->RemoveColor(fCurrentStepIndex);
249					if (update) {
250						fCurrentStepIndex = max_c(0, fCurrentStepIndex - 1);
251						_UpdateCurrentColor();
252					}
253					break;
254
255				case B_HOME:
256				case B_END:
257				case B_LEFT_ARROW:
258				case B_RIGHT_ARROW: {
259					if (BGradient::ColorStop* step
260							= fGradient->ColorAt(fCurrentStepIndex)) {
261						BRect r = _GradientBitmapRect();
262						float x = r.left + r.Width() * step->offset;
263						switch (bytes[0]) {
264							case B_LEFT_ARROW:
265								// move step to the left
266								x = max_c(r.left, x - 1.0);
267								break;
268							case B_RIGHT_ARROW:
269								// move step to the right
270								x = min_c(r.right, x + 1.0);
271								break;
272							case B_HOME:
273								// move step to the start
274								x = r.left;
275								break;
276							case B_END:
277								// move step to the start
278								x = r.right;
279								break;
280						}
281						update = fGradient->SetOffset(fCurrentStepIndex,
282							(x - r.left) / r.Width());
283					}
284					break;
285				}
286
287				case B_UP_ARROW:
288					// previous step
289					fCurrentStepIndex--;
290					if (fCurrentStepIndex < 0) {
291						fCurrentStepIndex = count - 1;
292					}
293					_UpdateCurrentColor();
294					break;
295				case B_DOWN_ARROW:
296					// next step
297					fCurrentStepIndex++;
298					if (fCurrentStepIndex >= count) {
299						fCurrentStepIndex = 0;
300					}
301					_UpdateCurrentColor();
302					break;
303
304				default:
305					handled = false;
306					break;
307			}
308		}
309	}
310	if (!handled)
311		BView::KeyDown(bytes, numBytes);
312	else {
313		if (update)
314			_UpdateColors();
315		Invalidate();
316	}
317}
318
319// Draw
320void
321GradientControl::Draw(BRect updateRect)
322{
323	BRect b = _GradientBitmapRect();
324	b.InsetBy(-2.0, -2.0);
325	// background
326	// left of gradient rect
327	BRect lb(updateRect.left, updateRect.top, b.left - 1, b.bottom);
328	if (lb.IsValid())
329		FillRect(lb, B_SOLID_LOW);
330	// right of gradient rect
331	BRect rb(b.right + 1, updateRect.top, updateRect.right, b.bottom);
332	if (rb.IsValid())
333		FillRect(rb, B_SOLID_LOW);
334	// bottom of gradient rect
335	BRect bb(updateRect.left, b.bottom + 1, updateRect.right, updateRect.bottom);
336	if (bb.IsValid())
337		FillRect(bb, B_SOLID_LOW);
338
339	bool isFocus = IsFocus() && Window()->IsActive();
340
341	rgb_color bg = LowColor();
342	rgb_color black;
343	rgb_color shadow;
344	rgb_color darkShadow;
345	rgb_color light;
346
347	if (fEnabled) {
348		shadow = tint_color(bg, B_DARKEN_1_TINT);
349		darkShadow = tint_color(bg, B_DARKEN_3_TINT);
350		light = tint_color(bg, B_LIGHTEN_MAX_TINT);
351		black = tint_color(bg, B_DARKEN_MAX_TINT);
352	} else {
353		shadow = bg;
354		darkShadow = tint_color(bg, B_DARKEN_1_TINT);
355		light = tint_color(bg, B_LIGHTEN_2_TINT);
356		black = tint_color(bg, B_DARKEN_2_TINT);
357	}
358
359	rgb_color focus = isFocus ? ui_color(B_KEYBOARD_NAVIGATION_COLOR) : black;
360
361	uint32 flags = 0;
362
363	if (be_control_look != NULL) {
364		if (!fEnabled)
365			flags |= BControlLook::B_DISABLED;
366		if (isFocus)
367			flags |= BControlLook::B_FOCUSED;
368		be_control_look->DrawTextControlBorder(this, b, updateRect, bg, flags);
369	} else {
370		stroke_frame(this, b, shadow, shadow, light, light);
371		b.InsetBy(1.0, 1.0);
372		if (isFocus)
373			stroke_frame(this, b, focus, focus, focus, focus);
374		else
375			stroke_frame(this, b, darkShadow, darkShadow, bg, bg);
376		b.InsetBy(1.0, 1.0);
377	}
378
379//	DrawBitmapAsync(fGradientBitmap, b.LeftTop());
380//	Sync();
381	DrawBitmap(fGradientBitmap, b.LeftTop());
382
383	// show drop offset
384	if (fDropOffset >= 0.0) {
385		SetHighColor(255, 0, 0, 255);
386		float x = b.left + b.Width() * fDropOffset;
387		StrokeLine(BPoint(x, b.top), BPoint(x, b.bottom));
388	}
389
390	BPoint markerPos;
391	markerPos.y = b.bottom + 4.0;
392	BPoint leftBottom(-6.0, 6.0);
393	BPoint rightBottom(6.0, 6.0);
394	for (int32 i = 0; BGradient::ColorStop* step = fGradient->ColorAt(i);
395			i++) {
396		markerPos.x = b.left + (b.Width() * step->offset);
397
398		if (i == fCurrentStepIndex) {
399			SetLowColor(focus);
400			if (isFocus) {
401				StrokeLine(markerPos + leftBottom + BPoint(1.0, 4.0),
402					markerPos + rightBottom + BPoint(-1.0, 4.0), B_SOLID_LOW);
403			}
404		} else {
405			SetLowColor(black);
406		}
407
408		// override in case this is the drop index step
409		if (i == fDropIndex)
410			SetLowColor(255, 0, 0, 255);
411
412		if (be_control_look != NULL) {
413			// TODO: Drop indication!
414			BRect rect(markerPos.x + leftBottom.x, markerPos.y,
415				markerPos.x + rightBottom.x, markerPos.y + rightBottom.y);
416			be_control_look->DrawSliderTriangle(this, rect, updateRect, bg,
417				step->color, flags, B_HORIZONTAL);
418		} else {
419			StrokeTriangle(markerPos, markerPos + leftBottom,
420				markerPos + rightBottom, B_SOLID_LOW);
421			if (fEnabled) {
422				SetHighColor(step->color);
423			} else {
424				rgb_color c = step->color;
425				c.red = (uint8)(((uint32)bg.red + (uint32)c.red) / 2);
426				c.green = (uint8)(((uint32)bg.green + (uint32)c.green) / 2);
427				c.blue = (uint8)(((uint32)bg.blue + (uint32)c.blue) / 2);
428				SetHighColor(c);
429			}
430			FillTriangle(markerPos + BPoint(0.0, 1.0),
431						 markerPos + leftBottom + BPoint(1.0, 0.0),
432						 markerPos + rightBottom + BPoint(-1.0, 0.0));
433			StrokeLine(markerPos + leftBottom + BPoint(0.0, 1.0),
434					   markerPos + rightBottom + BPoint(0.0, 1.0), B_SOLID_LOW);
435		}
436	}
437}
438
439// FrameResized
440void
441GradientControl::FrameResized(float width, float height)
442{
443	BRect r = _GradientBitmapRect();
444	_AllocBitmap(r.IntegerWidth() + 1, r.IntegerHeight() + 1);
445	_UpdateColors();
446	Invalidate();
447
448}
449
450// SetGradient
451void
452GradientControl::SetGradient(const ::Gradient* gradient)
453{
454	if (!gradient)
455		return;
456
457	*fGradient = *gradient;
458	_UpdateColors();
459
460	fDropOffset = -1.0;
461	fDropIndex = -1;
462	fDraggingStepIndex = -1;
463	if (fCurrentStepIndex > gradient->CountColors() - 1)
464		fCurrentStepIndex = gradient->CountColors() - 1;
465
466	Invalidate();
467}
468
469// SetCurrentStop
470void
471GradientControl::SetCurrentStop(const rgb_color& color)
472{
473	if (fEnabled && fCurrentStepIndex >= 0) {
474		fGradient->SetColor(fCurrentStepIndex, color);
475		_UpdateColors();
476		Invalidate();
477	}
478}
479
480// GetCurrentStop
481bool
482GradientControl::GetCurrentStop(rgb_color* color) const
483{
484	BGradient::ColorStop* stop
485		= fGradient->ColorAt(fCurrentStepIndex);
486	if (stop && color) {
487		*color = stop->color;
488		return true;
489	}
490	return false;
491}
492
493// SetEnabled
494void
495GradientControl::SetEnabled(bool enabled)
496{
497	if (enabled == fEnabled)
498		return;
499
500	fEnabled = enabled;
501
502	if (!fEnabled)
503		fDropIndex = -1;
504
505	_UpdateColors();
506	Invalidate();
507}
508
509// blend_colors
510inline void
511blend_colors(uint8* d, uint8 alpha, uint8 c1, uint8 c2, uint8 c3)
512{
513	if (alpha > 0) {
514		if (alpha == 255) {
515			d[0] = c1;
516			d[1] = c2;
517			d[2] = c3;
518		} else {
519			d[0] += (uint8)(((c1 - d[0]) * alpha) >> 8);
520			d[1] += (uint8)(((c2 - d[1]) * alpha) >> 8);
521			d[2] += (uint8)(((c3 - d[2]) * alpha) >> 8);
522		}
523	}
524}
525
526// _UpdateColors
527void
528GradientControl::_UpdateColors()
529{
530	if (!fGradientBitmap || !fGradientBitmap->IsValid())
531		return;
532
533	// fill in top row by gradient
534	uint8* topRow = (uint8*)fGradientBitmap->Bits();
535	uint32 width = fGradientBitmap->Bounds().IntegerWidth() + 1;
536	fGradient->MakeGradient((uint32*)topRow, width);
537	// flip colors, since they are the wrong endianess
538	// make colors the disabled look
539	// TODO: apply gamma lut
540	uint8* p = topRow;
541	if (!fEnabled) {
542		rgb_color bg = LowColor();
543		for (uint32 x = 0; x < width; x++) {
544			uint8 p0 = p[0];
545			p[0] = (uint8)(((uint32)bg.blue + (uint32)p[2]) / 2);
546			p[1] = (uint8)(((uint32)bg.green + (uint32)p[1]) / 2);
547			p[2] = (uint8)(((uint32)bg.red + (uint32)p0) / 2);
548			p += 4;
549		}
550	} else {
551		for (uint32 x = 0; x < width; x++) {
552			uint8 p0 = p[0];
553			p[0] = p[2];
554			p[2] = p0;
555			p += 4;
556		}
557	}
558	// copy top row to rest of bitmap
559	uint32 height = fGradientBitmap->Bounds().IntegerHeight() + 1;
560	uint32 bpr = fGradientBitmap->BytesPerRow();
561	uint8* dstRow = topRow + bpr;
562	for (uint32 i = 1; i < height; i++) {
563		memcpy(dstRow, topRow, bpr);
564		dstRow += bpr;
565	}
566	// post process bitmap to underlay it with a pattern
567	// in order to make gradient steps with alpha more visible!
568	uint8* row = topRow;
569	for (uint32 i = 0; i < height; i++) {
570		uint8* p = row;
571		for (uint32 x = 0; x < width; x++) {
572			uint8 alpha = p[3];
573			if (alpha < 255) {
574				p[3] = 255;
575				alpha = 255 - alpha;
576				if (x % 8 >= 4) {
577					if (i % 8 >= 4) {
578						blend_colors(p, alpha,
579									 kAlphaLow.blue,
580									 kAlphaLow.green,
581									 kAlphaLow.red);
582					} else {
583						blend_colors(p, alpha,
584									 kAlphaHigh.blue,
585									 kAlphaHigh.green,
586									 kAlphaHigh.red);
587					}
588				} else {
589					if (i % 8 >= 4) {
590						blend_colors(p, alpha,
591									 kAlphaHigh.blue,
592									 kAlphaHigh.green,
593									 kAlphaHigh.red);
594					} else {
595						blend_colors(p, alpha,
596									 kAlphaLow.blue,
597									 kAlphaLow.green,
598									 kAlphaLow.red);
599					}
600				}
601			}
602			p += 4;
603		}
604		row += bpr;
605	}
606}
607
608// _AllocBitmap
609void
610GradientControl::_AllocBitmap(int32 width, int32 height)
611{
612	if (width < 2 || height < 2)
613		return;
614
615	delete fGradientBitmap;
616	fGradientBitmap = new BBitmap(BRect(0, 0, width - 1, height - 1), 0, B_RGB32);
617}
618
619// _GradientBitmapRect
620BRect
621GradientControl::_GradientBitmapRect() const
622{
623	BRect r = Bounds();
624	r.left += 6.0;
625	r.top += 2.0;
626	r.right -= 6.0;
627	r.bottom -= 14.0;
628	return r;
629}
630
631// _StepIndexFor
632int32
633GradientControl::_StepIndexFor(BPoint where) const
634{
635	int32 index = -1;
636	BRect r = _GradientBitmapRect();
637	BRect markerFrame(Bounds());
638	for (int32 i = 0; BGradient::ColorStop* step
639			= fGradient->ColorAt(i); i++) {
640		markerFrame.left = markerFrame.right = r.left
641			+ (r.Width() * step->offset);
642		markerFrame.InsetBy(-6.0, 0.0);
643		if (markerFrame.Contains(where)) {
644			index = i;
645			break;
646		}
647	}
648	return index;
649}
650
651// _OffsetFor
652float
653GradientControl::_OffsetFor(BPoint where) const
654{
655	BRect r = _GradientBitmapRect();
656	float offset = (where.x - r.left) / r.Width();
657	offset = max_c(0.0, offset);
658	offset = min_c(1.0, offset);
659	return offset;
660}
661
662// _UpdateCurrentColor
663void
664GradientControl::_UpdateCurrentColor() const
665{
666	if (!fMessage || !fTarget || !fTarget->Looper())
667		return;
668	// set the CanvasView current color
669	if (BGradient::ColorStop* step = fGradient->ColorAt(fCurrentStepIndex)) {
670		BMessage message(*fMessage);
671		store_color_in_message(&message, step->color);
672		fTarget->Looper()->PostMessage(&message, fTarget);
673	}
674}
675