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