xref: /haiku/src/apps/icon-o-matic/gui/GradientControl.cpp (revision 1acbe440b8dd798953bec31d18ee589aa3f71b73)
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 "Gradient.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 		color_step* 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 (color_step* step = fGradient->ColorAt(fDropIndex)) {
205 							color.alpha = step->color.alpha;
206 						}
207 						fGradient->SetColor(fDropIndex, color);
208 						fCurrentStepIndex = fDropIndex;
209 						fDropIndex = -1;
210 						update = true;
211 					} else if (fDropOffset >= 0.0) {
212 						fCurrentStepIndex = fGradient->AddColor(color, fDropOffset);
213 						fDropOffset = -1.0;
214 						update = true;
215 					}
216 					if (update) {
217 						_UpdateColors();
218 						if (!IsFocus())
219 							MakeFocus(true);
220 						else
221 							Invalidate();
222 						_UpdateCurrentColor();
223 					}
224 				}
225 			}
226 			break;
227 		default:
228 			BView::MessageReceived(message);
229 	}
230 }
231 
232 // KeyDown
233 void
234 GradientControl::KeyDown(const char* bytes, int32 numBytes)
235 {
236 	bool handled = false;
237 	bool update = false;
238 	if (fEnabled) {
239 		if (numBytes > 0) {
240 			handled = true;
241 			int32 count = fGradient->CountColors();
242 			switch (bytes[0]) {
243 				case B_DELETE:
244 					// remove step
245 					update = fGradient->RemoveColor(fCurrentStepIndex);
246 					if (update) {
247 						fCurrentStepIndex = max_c(0, fCurrentStepIndex - 1);
248 						_UpdateCurrentColor();
249 					}
250 					break;
251 
252 				case B_HOME:
253 				case B_END:
254 				case B_LEFT_ARROW:
255 				case B_RIGHT_ARROW: {
256 					if (color_step* step = fGradient->ColorAt(fCurrentStepIndex)) {
257 						BRect r = _GradientBitmapRect();
258 						float x = r.left + r.Width() * step->offset;
259 						switch (bytes[0]) {
260 							case B_LEFT_ARROW:
261 								// move step to the left
262 								x = max_c(r.left, x - 1.0);
263 								break;
264 							case B_RIGHT_ARROW:
265 								// move step to the right
266 								x = min_c(r.right, x + 1.0);
267 								break;
268 							case B_HOME:
269 								// move step to the start
270 								x = r.left;
271 								break;
272 							case B_END:
273 								// move step to the start
274 								x = r.right;
275 								break;
276 						}
277 						update = fGradient->SetOffset(fCurrentStepIndex, (x - r.left) / r.Width());
278 					}
279 					break;
280 				}
281 
282 				case B_UP_ARROW:
283 					// previous step
284 					fCurrentStepIndex--;
285 					if (fCurrentStepIndex < 0) {
286 						fCurrentStepIndex = count - 1;
287 					}
288 					_UpdateCurrentColor();
289 					break;
290 				case B_DOWN_ARROW:
291 					// next step
292 					fCurrentStepIndex++;
293 					if (fCurrentStepIndex >= count) {
294 						fCurrentStepIndex = 0;
295 					}
296 					_UpdateCurrentColor();
297 					break;
298 
299 				default:
300 					handled = false;
301 					break;
302 			}
303 		}
304 	}
305 	if (!handled)
306 		BView::KeyDown(bytes, numBytes);
307 	else {
308 		if (update)
309 			_UpdateColors();
310 		Invalidate();
311 	}
312 }
313 
314 // Draw
315 void
316 GradientControl::Draw(BRect updateRect)
317 {
318 	BRect b = _GradientBitmapRect();
319 	b.InsetBy(-2.0, -2.0);
320 	// background
321 	// left of gradient rect
322 	BRect lb(updateRect.left, updateRect.top, b.left - 1, b.bottom);
323 	if (lb.IsValid())
324 		FillRect(lb, B_SOLID_LOW);
325 	// right of gradient rect
326 	BRect rb(b.right + 1, updateRect.top, updateRect.right, b.bottom);
327 	if (rb.IsValid())
328 		FillRect(rb, B_SOLID_LOW);
329 	// bottom of gradient rect
330 	BRect bb(updateRect.left, b.bottom + 1, updateRect.right, updateRect.bottom);
331 	if (bb.IsValid())
332 		FillRect(bb, B_SOLID_LOW);
333 
334 	bool isFocus = IsFocus() && Window()->IsActive();
335 
336 	rgb_color bg = LowColor();
337 	rgb_color shadow;
338 	rgb_color darkShadow;
339 	rgb_color light;
340 	rgb_color black;
341 
342 	if (fEnabled) {
343 		shadow = tint_color(bg, B_DARKEN_1_TINT);
344 		darkShadow = tint_color(bg, B_DARKEN_3_TINT);
345 		light = tint_color(bg, B_LIGHTEN_MAX_TINT);
346 		black = tint_color(bg, B_DARKEN_MAX_TINT);
347 	} else {
348 		shadow = bg;
349 		darkShadow = tint_color(bg, B_DARKEN_1_TINT);
350 		light = tint_color(bg, B_LIGHTEN_2_TINT);
351 		black = tint_color(bg, B_DARKEN_2_TINT);
352 	}
353 
354 	rgb_color focus = isFocus ? ui_color(B_KEYBOARD_NAVIGATION_COLOR)
355 							  : black;
356 
357 	stroke_frame(this, b, shadow, shadow, light, light);
358 	b.InsetBy(1.0, 1.0);
359 	if (isFocus)
360 		stroke_frame(this, b, focus, focus, focus, focus);
361 	else
362 		stroke_frame(this, b, darkShadow, darkShadow, bg, bg);
363 	b.InsetBy(1.0, 1.0);
364 
365 //	DrawBitmapAsync(fGradientBitmap, b.LeftTop());
366 //	Sync();
367 	DrawBitmap(fGradientBitmap, b.LeftTop());
368 
369 	// show drop offset
370 	if (fDropOffset >= 0.0) {
371 		SetHighColor(255, 0, 0, 255);
372 		float x = b.left + b.Width() * fDropOffset;
373 		StrokeLine(BPoint(x, b.top), BPoint(x, b.bottom));
374 	}
375 
376 	BPoint markerPos;
377 	markerPos.y = b.bottom + 4.0;
378 	BPoint leftBottom(-6.0, 6.0);
379 	BPoint rightBottom(6.0, 6.0);
380 	for (int32 i = 0; color_step* step = fGradient->ColorAt(i); i++) {
381 		markerPos.x = b.left + (b.Width() * step->offset);
382 
383 		if (i == fCurrentStepIndex) {
384 			SetLowColor(focus);
385 			if (isFocus) {
386 				StrokeLine(markerPos + leftBottom + BPoint(1.0, 4.0),
387 						   markerPos + rightBottom + BPoint(-1.0, 4.0), B_SOLID_LOW);
388 			}
389 		} else {
390 			SetLowColor(black);
391 		}
392 
393 		// override in case this is the drop index step
394 		if (i == fDropIndex)
395 			SetLowColor(255, 0, 0, 255);
396 
397 		StrokeTriangle(markerPos,
398 					   markerPos + leftBottom,
399 					   markerPos + rightBottom, B_SOLID_LOW);
400 		if (fEnabled) {
401 			SetHighColor(step->color);
402 		} else {
403 			rgb_color c = step->color;
404 			c.red = (uint8)(((uint32)bg.red + (uint32)c.red) / 2);
405 			c.green = (uint8)(((uint32)bg.green + (uint32)c.green) / 2);
406 			c.blue = (uint8)(((uint32)bg.blue + (uint32)c.blue) / 2);
407 			SetHighColor(c);
408 		}
409 		FillTriangle(markerPos + BPoint(0.0, 1.0),
410 					 markerPos + leftBottom + BPoint(1.0, 0.0),
411 					 markerPos + rightBottom + BPoint(-1.0, 0.0));
412 		StrokeLine(markerPos + leftBottom + BPoint(0.0, 1.0),
413 				   markerPos + rightBottom + BPoint(0.0, 1.0), B_SOLID_LOW);
414 	}
415 }
416 
417 // FrameResized
418 void
419 GradientControl::FrameResized(float width, float height)
420 {
421 	BRect r = _GradientBitmapRect();
422 	_AllocBitmap(r.IntegerWidth() + 1, r.IntegerHeight() + 1);
423 	_UpdateColors();
424 	Invalidate();
425 
426 }
427 
428 // SetGradient
429 void
430 GradientControl::SetGradient(const ::Gradient* gradient)
431 {
432 	if (!gradient)
433 		return;
434 
435 	*fGradient = *gradient;
436 	_UpdateColors();
437 
438 	fDropOffset = -1.0;
439 	fDropIndex = -1;
440 	fDraggingStepIndex = -1;
441 	if (fCurrentStepIndex > gradient->CountColors() - 1)
442 		fCurrentStepIndex = gradient->CountColors() - 1;
443 
444 	Invalidate();
445 }
446 
447 // SetCurrentStop
448 void
449 GradientControl::SetCurrentStop(const rgb_color& color)
450 {
451 	if (fEnabled && fCurrentStepIndex >= 0) {
452 		fGradient->SetColor(fCurrentStepIndex, color);
453 		_UpdateColors();
454 		Invalidate();
455 	}
456 }
457 
458 // GetCurrentStop
459 bool
460 GradientControl::GetCurrentStop(rgb_color* color) const
461 {
462 	color_step* stop = fGradient->ColorAt(fCurrentStepIndex);
463 	if (stop && color) {
464 		*color = stop->color;
465 		return true;
466 	}
467 	return false;
468 }
469 
470 // SetEnabled
471 void
472 GradientControl::SetEnabled(bool enabled)
473 {
474 	if (enabled == fEnabled)
475 		return;
476 
477 	fEnabled = enabled;
478 
479 	if (!fEnabled)
480 		fDropIndex = -1;
481 
482 	_UpdateColors();
483 	Invalidate();
484 }
485 
486 // blend_colors
487 inline void
488 blend_colors(uint8* d, uint8 alpha, uint8 c1, uint8 c2, uint8 c3)
489 {
490 	if (alpha > 0) {
491 		if (alpha == 255) {
492 			d[0] = c1;
493 			d[1] = c2;
494 			d[2] = c3;
495 		} else {
496 			d[0] += (uint8)(((c1 - d[0]) * alpha) >> 8);
497 			d[1] += (uint8)(((c2 - d[1]) * alpha) >> 8);
498 			d[2] += (uint8)(((c3 - d[2]) * alpha) >> 8);
499 		}
500 	}
501 }
502 
503 // _UpdateColors
504 void
505 GradientControl::_UpdateColors()
506 {
507 	if (!fGradientBitmap || !fGradientBitmap->IsValid())
508 		return;
509 
510 	// fill in top row by gradient
511 	uint8* topRow = (uint8*)fGradientBitmap->Bits();
512 	uint32 width = fGradientBitmap->Bounds().IntegerWidth() + 1;
513 	fGradient->MakeGradient((uint32*)topRow, width);
514 	// flip colors, since they are the wrong endianess
515 	// make colors the disabled look
516 	// TODO: apply gamma lut
517 	uint8* p = topRow;
518 	if (!fEnabled) {
519 		rgb_color bg = LowColor();
520 		for (uint32 x = 0; x < width; x++) {
521 			uint8 p0 = p[0];
522 			p[0] = (uint8)(((uint32)bg.blue + (uint32)p[2]) / 2);
523 			p[1] = (uint8)(((uint32)bg.green + (uint32)p[1]) / 2);
524 			p[2] = (uint8)(((uint32)bg.red + (uint32)p0) / 2);
525 			p += 4;
526 		}
527 	} else {
528 		for (uint32 x = 0; x < width; x++) {
529 			uint8 p0 = p[0];
530 			p[0] = p[2];
531 			p[2] = p0;
532 			p += 4;
533 		}
534 	}
535 	// copy top row to rest of bitmap
536 	uint32 height = fGradientBitmap->Bounds().IntegerHeight() + 1;
537 	uint32 bpr = fGradientBitmap->BytesPerRow();
538 	uint8* dstRow = topRow + bpr;
539 	for (uint32 i = 1; i < height; i++) {
540 		memcpy(dstRow, topRow, bpr);
541 		dstRow += bpr;
542 	}
543 	// post process bitmap to underlay it with a pattern
544 	// in order to make gradient steps with alpha more visible!
545 	uint8* row = topRow;
546 	for (uint32 i = 0; i < height; i++) {
547 		uint8* p = row;
548 		for (uint32 x = 0; x < width; x++) {
549 			uint8 alpha = p[3];
550 			if (alpha < 255) {
551 				p[3] = 255;
552 				alpha = 255 - alpha;
553 				if (x % 8 >= 4) {
554 					if (i % 8 >= 4) {
555 						blend_colors(p, alpha,
556 									 kAlphaLow.blue,
557 									 kAlphaLow.green,
558 									 kAlphaLow.red);
559 					} else {
560 						blend_colors(p, alpha,
561 									 kAlphaHigh.blue,
562 									 kAlphaHigh.green,
563 									 kAlphaHigh.red);
564 					}
565 				} else {
566 					if (i % 8 >= 4) {
567 						blend_colors(p, alpha,
568 									 kAlphaHigh.blue,
569 									 kAlphaHigh.green,
570 									 kAlphaHigh.red);
571 					} else {
572 						blend_colors(p, alpha,
573 									 kAlphaLow.blue,
574 									 kAlphaLow.green,
575 									 kAlphaLow.red);
576 					}
577 				}
578 			}
579 			p += 4;
580 		}
581 		row += bpr;
582 	}
583 }
584 
585 // _AllocBitmap
586 void
587 GradientControl::_AllocBitmap(int32 width, int32 height)
588 {
589 	if (width < 2 || height < 2)
590 		return;
591 
592 	delete fGradientBitmap;
593 	fGradientBitmap = new BBitmap(BRect(0, 0, width - 1, height - 1), 0, B_RGB32);
594 }
595 
596 // _GradientBitmapRect
597 BRect
598 GradientControl::_GradientBitmapRect() const
599 {
600 	BRect r = Bounds();
601 	r.left += 6.0;
602 	r.top += 2.0;
603 	r.right -= 6.0;
604 	r.bottom -= 14.0;
605 	return r;
606 }
607 
608 // _StepIndexFor
609 int32
610 GradientControl::_StepIndexFor(BPoint where) const
611 {
612 	int32 index = -1;
613 	BRect r = _GradientBitmapRect();
614 	BRect markerFrame(Bounds());
615 	for (int32 i = 0; color_step* step = fGradient->ColorAt(i); i++) {
616 		markerFrame.left = markerFrame.right = r.left + (r.Width() * step->offset);
617 		markerFrame.InsetBy(-6.0, 0.0);
618 		if (markerFrame.Contains(where)) {
619 			index = i;
620 			break;
621 		}
622 	}
623 	return index;
624 }
625 
626 // _OffsetFor
627 float
628 GradientControl::_OffsetFor(BPoint where) const
629 {
630 	BRect r = _GradientBitmapRect();
631 	float offset = (where.x - r.left) / r.Width();
632 	offset = max_c(0.0, offset);
633 	offset = min_c(1.0, offset);
634 	return offset;
635 }
636 
637 // _UpdateCurrentColor
638 void
639 GradientControl::_UpdateCurrentColor() const
640 {
641 	if (!fMessage || !fTarget || !fTarget->Looper())
642 		return;
643 	// set the CanvasView current color
644 	if (color_step* step = fGradient->ColorAt(fCurrentStepIndex)) {
645 		BMessage message(*fMessage);
646 		store_color_in_message(&message, step->color);
647 		fTarget->Looper()->PostMessage(&message, fTarget);
648 	}
649 }
650