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