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