xref: /haiku/src/apps/icon-o-matic/gui/GradientControl.cpp (revision 68ea01249e1e2088933cb12f9c28d4e5c5d1c9ef)
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
25 GradientControl::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
44 GradientControl::~GradientControl()
45 {
46 	delete fGradient;
47 	delete fGradientBitmap;
48 	delete fMessage;
49 }
50 
51 #if LIB_LAYOUT
52 // layoutprefs
53 minimax
54 GradientControl::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
67 BRect
68 GradientControl::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
77 void
78 GradientControl::WindowActivated(bool active)
79 {
80 	if (IsFocus())
81 		Invalidate();
82 }
83 
84 // MakeFocus
85 void
86 GradientControl::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
100 void
101 GradientControl::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
152 void
153 GradientControl::MouseUp(BPoint where)
154 {
155 	fDraggingStepIndex = -1;
156 }
157 
158 // MouseMoved
159 void
160 GradientControl::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
195 void
196 GradientControl::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
236 void
237 GradientControl::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
320 void
321 GradientControl::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
440 void
441 GradientControl::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
451 void
452 GradientControl::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
470 void
471 GradientControl::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
481 bool
482 GradientControl::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
494 void
495 GradientControl::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
510 inline void
511 blend_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
527 void
528 GradientControl::_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
609 void
610 GradientControl::_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
620 BRect
621 GradientControl::_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
632 int32
633 GradientControl::_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
652 float
653 GradientControl::_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
663 void
664 GradientControl::_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