xref: /haiku/src/apps/icon-o-matic/gui/GradientControl.cpp (revision da4dbfa47a47beb355289f3dd685797cee69ab77)
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 void
54 GradientControl::WindowActivated(bool active)
55 {
56 	if (IsFocus())
57 		Invalidate();
58 }
59 
60 
61 void
62 GradientControl::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 
76 void
77 GradientControl::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 
128 void
129 GradientControl::MouseUp(BPoint where)
130 {
131 	fDraggingStepIndex = -1;
132 }
133 
134 
135 void
136 GradientControl::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 
171 void
172 GradientControl::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 
212 void
213 GradientControl::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 
296 void
297 GradientControl::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 
416 void
417 GradientControl::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 
427 void
428 GradientControl::GetPreferredSize(float* width, float* height)
429 {
430 	if (width != NULL)
431 		*width = 100;
432 
433 	if (height != NULL)
434 		*height = 19;
435 }
436 
437 
438 void
439 GradientControl::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 
457 void
458 GradientControl::SetCurrentStop(const rgb_color& color)
459 {
460 	if (fEnabled && fCurrentStepIndex >= 0) {
461 		fGradient->SetColor(fCurrentStepIndex, color);
462 		_UpdateColors();
463 		Invalidate();
464 	}
465 }
466 
467 
468 bool
469 GradientControl::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 
481 void
482 GradientControl::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 
497 inline void
498 blend_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 
514 void
515 GradientControl::_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 
596 void
597 GradientControl::_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 
607 BRect
608 GradientControl::_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 
619 int32
620 GradientControl::_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 
639 float
640 GradientControl::_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 
650 void
651 GradientControl::_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