* Copyright 2006, 2023, Haiku.
* Distributed under the terms of the MIT License.
*
* Authors:
* Stephan Aßmus <superstippi@gmx.de>
* Zardshard
*/
#include "GradientControl.h"
#include <stdio.h>
#include <AppDefs.h>
#include <Bitmap.h>
#include <ControlLook.h>
#include <Message.h>
#include <Window.h>
#include "ui_defines.h"
#include "support_ui.h"
#include "GradientTransformable.h"
GradientControl::GradientControl(BMessage* message, BHandler* target)
: BView(BRect(0, 0, 100, 19), "gradient control", B_FOLLOW_NONE,
B_WILL_DRAW | B_FRAME_EVENTS | B_NAVIGABLE),
fGradient(new ::Gradient()),
fGradientBitmap(NULL),
fDraggingStepIndex(-1),
fCurrentStepIndex(-1),
fDropOffset(-1.0),
fDropIndex(-1),
fEnabled(true),
fMessage(message),
fTarget(target)
{
FrameResized(Bounds().Width(), Bounds().Height());
SetViewColor(B_TRANSPARENT_32_BIT);
SetLowColor(ui_color(B_PANEL_BACKGROUND_COLOR));
}
GradientControl::~GradientControl()
{
delete fGradient;
delete fGradientBitmap;
delete fMessage;
}
void
GradientControl::WindowActivated(bool active)
{
if (IsFocus())
Invalidate();
}
void
GradientControl::MakeFocus(bool focus)
{
if (focus != IsFocus()) {
_UpdateCurrentColor();
Invalidate();
if (fTarget) {
if (BLooper* looper = fTarget->Looper())
looper->PostMessage(MSG_GRADIENT_CONTROL_FOCUS_CHANGED, fTarget);
}
}
BView::MakeFocus(focus);
}
void
GradientControl::MouseDown(BPoint where)
{
if (!fEnabled)
return;
if (!IsFocus()) {
MakeFocus(true);
}
fDraggingStepIndex = _StepIndexFor(where);
if (fDraggingStepIndex >= 0)
SetMouseEventMask(B_POINTER_EVENTS, B_LOCK_WINDOW_FOCUS);
int32 clicks;
if (Window()->CurrentMessage()->FindInt32("clicks", &clicks) >= B_OK && clicks >= 2) {
if (fDraggingStepIndex < 0) {
float offset = _OffsetFor(where);
uint32 width = fGradientBitmap->Bounds().IntegerWidth();
uint8* temp = new uint8[width * 4];
fGradient->MakeGradient((uint32*)temp, width);
rgb_color color;
uint8* bits = temp;
bits += 4 * (uint32)((width - 1) * offset);
color.red = bits[0];
color.green = bits[1];
color.blue = bits[2];
color.alpha = bits[3];
fCurrentStepIndex = fGradient->AddColor(color, offset);
fDraggingStepIndex = -1;
_UpdateColors();
Invalidate();
_UpdateCurrentColor();
delete[] temp;
}
}
if (fCurrentStepIndex != fDraggingStepIndex && fDraggingStepIndex >= 0) {
fCurrentStepIndex = fDraggingStepIndex;
Invalidate();
_UpdateCurrentColor();
}
}
void
GradientControl::MouseUp(BPoint where)
{
fDraggingStepIndex = -1;
}
void
GradientControl::MouseMoved(BPoint where, uint32 transit, const BMessage* dragMessage)
{
if (!fEnabled)
return;
float offset = _OffsetFor(where);
if (fDraggingStepIndex >= 0) {
BGradient::ColorStop* step = fGradient->ColorAt(fDraggingStepIndex);
if (step) {
if (fGradient->SetOffset(fDraggingStepIndex, offset)) {
_UpdateColors();
Invalidate();
}
}
}
int32 dropIndex = -1;
float dropOffset = -1.0;
if (dragMessage && (transit == B_INSIDE_VIEW || transit == B_ENTERED_VIEW)) {
rgb_color dragColor;
if (restore_color_from_message(dragMessage, dragColor, 0) >= B_OK) {
dropIndex = _StepIndexFor(where);
if (dropIndex < 0)
dropOffset = offset;
}
}
if (fDropOffset != dropOffset || fDropIndex != dropIndex) {
fDropOffset = dropOffset;
fDropIndex = dropIndex;
Invalidate();
}
}
void
GradientControl::MessageReceived(BMessage* message)
{
switch (message->what) {
case B_PASTE:
if (fEnabled) {
rgb_color color;
if (restore_color_from_message(message, color, 0) >= B_OK) {
bool update = false;
if (fDropIndex >= 0) {
if (BGradient::ColorStop* step
= fGradient->ColorAt(fDropIndex)) {
color.alpha = step->color.alpha;
}
fGradient->SetColor(fDropIndex, color);
fCurrentStepIndex = fDropIndex;
fDropIndex = -1;
update = true;
} else if (fDropOffset >= 0.0) {
fCurrentStepIndex = fGradient->AddColor(color,
fDropOffset);
fDropOffset = -1.0;
update = true;
}
if (update) {
_UpdateColors();
if (!IsFocus())
MakeFocus(true);
else
Invalidate();
_UpdateCurrentColor();
}
}
}
break;
default:
BView::MessageReceived(message);
}
}
void
GradientControl::KeyDown(const char* bytes, int32 numBytes)
{
bool handled = false;
bool update = false;
if (fEnabled) {
if (numBytes > 0) {
handled = true;
int32 count = fGradient->CountColors();
switch (bytes[0]) {
case B_DELETE:
update = fGradient->RemoveColor(fCurrentStepIndex);
if (update) {
fCurrentStepIndex = max_c(0, fCurrentStepIndex - 1);
_UpdateCurrentColor();
}
break;
case B_HOME:
case B_END:
case B_LEFT_ARROW:
case B_RIGHT_ARROW: {
if (BGradient::ColorStop* step
= fGradient->ColorAt(fCurrentStepIndex)) {
BRect r = _GradientBitmapRect();
float x = r.left + r.Width() * step->offset;
switch (bytes[0]) {
case B_LEFT_ARROW:
x = max_c(r.left, x - 1.0);
break;
case B_RIGHT_ARROW:
x = min_c(r.right, x + 1.0);
break;
case B_HOME:
x = r.left;
break;
case B_END:
x = r.right;
break;
}
update = fGradient->SetOffset(fCurrentStepIndex,
(x - r.left) / r.Width());
}
break;
}
case B_UP_ARROW:
fCurrentStepIndex--;
if (fCurrentStepIndex < 0) {
fCurrentStepIndex = count - 1;
}
_UpdateCurrentColor();
break;
case B_DOWN_ARROW:
fCurrentStepIndex++;
if (fCurrentStepIndex >= count) {
fCurrentStepIndex = 0;
}
_UpdateCurrentColor();
break;
default:
handled = false;
break;
}
}
}
if (!handled)
BView::KeyDown(bytes, numBytes);
else {
if (update)
_UpdateColors();
Invalidate();
}
}
void
GradientControl::Draw(BRect updateRect)
{
BRect b = _GradientBitmapRect();
b.InsetBy(-2.0, -2.0);
BRect lb(updateRect.left, updateRect.top, b.left - 1, b.bottom);
if (lb.IsValid())
FillRect(lb, B_SOLID_LOW);
BRect rb(b.right + 1, updateRect.top, updateRect.right, b.bottom);
if (rb.IsValid())
FillRect(rb, B_SOLID_LOW);
BRect bb(updateRect.left, b.bottom + 1, updateRect.right, updateRect.bottom);
if (bb.IsValid())
FillRect(bb, B_SOLID_LOW);
bool isFocus = IsFocus() && Window()->IsActive();
rgb_color bg = LowColor();
rgb_color black;
rgb_color shadow;
rgb_color darkShadow;
rgb_color light;
if (fEnabled) {
shadow = tint_color(bg, B_DARKEN_1_TINT);
darkShadow = tint_color(bg, B_DARKEN_3_TINT);
light = tint_color(bg, B_LIGHTEN_MAX_TINT);
black = tint_color(bg, B_DARKEN_MAX_TINT);
} else {
shadow = bg;
darkShadow = tint_color(bg, B_DARKEN_1_TINT);
light = tint_color(bg, B_LIGHTEN_2_TINT);
black = tint_color(bg, B_DARKEN_2_TINT);
}
rgb_color focus = isFocus ? ui_color(B_KEYBOARD_NAVIGATION_COLOR) : black;
uint32 flags = 0;
if (be_control_look != NULL) {
if (!fEnabled)
flags |= BControlLook::B_DISABLED;
if (isFocus)
flags |= BControlLook::B_FOCUSED;
be_control_look->DrawTextControlBorder(this, b, updateRect, bg, flags);
} else {
stroke_frame(this, b, shadow, shadow, light, light);
b.InsetBy(1.0, 1.0);
if (isFocus)
stroke_frame(this, b, focus, focus, focus, focus);
else
stroke_frame(this, b, darkShadow, darkShadow, bg, bg);
b.InsetBy(1.0, 1.0);
}
DrawBitmap(fGradientBitmap, b.LeftTop());
if (fDropOffset >= 0.0) {
SetHighColor(255, 0, 0, 255);
float x = b.left + b.Width() * fDropOffset;
StrokeLine(BPoint(x, b.top), BPoint(x, b.bottom));
}
BPoint markerPos;
markerPos.y = b.bottom + 4.0;
BPoint leftBottom(-6.0, 6.0);
BPoint rightBottom(6.0, 6.0);
for (int32 i = 0; BGradient::ColorStop* step = fGradient->ColorAt(i);
i++) {
markerPos.x = b.left + (b.Width() * step->offset);
if (i == fCurrentStepIndex) {
SetLowColor(focus);
if (isFocus) {
StrokeLine(markerPos + leftBottom + BPoint(1.0, 4.0),
markerPos + rightBottom + BPoint(-1.0, 4.0), B_SOLID_LOW);
}
} else {
SetLowColor(black);
}
if (i == fDropIndex)
SetLowColor(255, 0, 0, 255);
if (be_control_look != NULL) {
BRect rect(markerPos.x + leftBottom.x, markerPos.y,
markerPos.x + rightBottom.x, markerPos.y + rightBottom.y);
be_control_look->DrawSliderTriangle(this, rect, updateRect, bg,
step->color, flags, B_HORIZONTAL);
} else {
StrokeTriangle(markerPos, markerPos + leftBottom,
markerPos + rightBottom, B_SOLID_LOW);
if (fEnabled) {
SetHighColor(step->color);
} else {
rgb_color c = step->color;
c.red = (uint8)(((uint32)bg.red + (uint32)c.red) / 2);
c.green = (uint8)(((uint32)bg.green + (uint32)c.green) / 2);
c.blue = (uint8)(((uint32)bg.blue + (uint32)c.blue) / 2);
SetHighColor(c);
}
FillTriangle(markerPos + BPoint(0.0, 1.0),
markerPos + leftBottom + BPoint(1.0, 0.0),
markerPos + rightBottom + BPoint(-1.0, 0.0));
StrokeLine(markerPos + leftBottom + BPoint(0.0, 1.0),
markerPos + rightBottom + BPoint(0.0, 1.0), B_SOLID_LOW);
}
}
}
void
GradientControl::FrameResized(float width, float height)
{
BRect r = _GradientBitmapRect();
_AllocBitmap(r.IntegerWidth() + 1, r.IntegerHeight() + 1);
_UpdateColors();
Invalidate();
}
void
GradientControl::GetPreferredSize(float* width, float* height)
{
if (width != NULL)
*width = 100;
if (height != NULL)
*height = 19;
}
void
GradientControl::SetGradient(const ::Gradient* gradient)
{
if (!gradient)
return;
*fGradient = *gradient;
_UpdateColors();
fDropOffset = -1.0;
fDropIndex = -1;
fDraggingStepIndex = -1;
if (fCurrentStepIndex > gradient->CountColors() - 1)
fCurrentStepIndex = gradient->CountColors() - 1;
Invalidate();
}
void
GradientControl::SetCurrentStop(const rgb_color& color)
{
if (fEnabled && fCurrentStepIndex >= 0) {
fGradient->SetColor(fCurrentStepIndex, color);
_UpdateColors();
Invalidate();
}
}
bool
GradientControl::GetCurrentStop(rgb_color* color) const
{
BGradient::ColorStop* stop
= fGradient->ColorAt(fCurrentStepIndex);
if (stop && color) {
*color = stop->color;
return true;
}
return false;
}
void
GradientControl::SetEnabled(bool enabled)
{
if (enabled == fEnabled)
return;
fEnabled = enabled;
if (!fEnabled)
fDropIndex = -1;
_UpdateColors();
Invalidate();
}
inline void
blend_colors(uint8* d, uint8 alpha, uint8 c1, uint8 c2, uint8 c3)
{
if (alpha > 0) {
if (alpha == 255) {
d[0] = c1;
d[1] = c2;
d[2] = c3;
} else {
d[0] += (uint8)(((c1 - d[0]) * alpha) >> 8);
d[1] += (uint8)(((c2 - d[1]) * alpha) >> 8);
d[2] += (uint8)(((c3 - d[2]) * alpha) >> 8);
}
}
}
void
GradientControl::_UpdateColors()
{
if (!fGradientBitmap || !fGradientBitmap->IsValid())
return;
uint8* topRow = (uint8*)fGradientBitmap->Bits();
uint32 width = fGradientBitmap->Bounds().IntegerWidth() + 1;
fGradient->MakeGradient((uint32*)topRow, width);
uint8* p = topRow;
if (!fEnabled) {
rgb_color bg = LowColor();
for (uint32 x = 0; x < width; x++) {
uint8 p0 = p[0];
p[0] = (uint8)(((uint32)bg.blue + (uint32)p[2]) / 2);
p[1] = (uint8)(((uint32)bg.green + (uint32)p[1]) / 2);
p[2] = (uint8)(((uint32)bg.red + (uint32)p0) / 2);
p += 4;
}
} else {
for (uint32 x = 0; x < width; x++) {
uint8 p0 = p[0];
p[0] = p[2];
p[2] = p0;
p += 4;
}
}
uint32 height = fGradientBitmap->Bounds().IntegerHeight() + 1;
uint32 bpr = fGradientBitmap->BytesPerRow();
uint8* dstRow = topRow + bpr;
for (uint32 i = 1; i < height; i++) {
memcpy(dstRow, topRow, bpr);
dstRow += bpr;
}
uint8* row = topRow;
for (uint32 i = 0; i < height; i++) {
uint8* p = row;
for (uint32 x = 0; x < width; x++) {
uint8 alpha = p[3];
if (alpha < 255) {
p[3] = 255;
alpha = 255 - alpha;
if (x % 8 >= 4) {
if (i % 8 >= 4) {
blend_colors(p, alpha,
kAlphaLow.blue,
kAlphaLow.green,
kAlphaLow.red);
} else {
blend_colors(p, alpha,
kAlphaHigh.blue,
kAlphaHigh.green,
kAlphaHigh.red);
}
} else {
if (i % 8 >= 4) {
blend_colors(p, alpha,
kAlphaHigh.blue,
kAlphaHigh.green,
kAlphaHigh.red);
} else {
blend_colors(p, alpha,
kAlphaLow.blue,
kAlphaLow.green,
kAlphaLow.red);
}
}
}
p += 4;
}
row += bpr;
}
}
void
GradientControl::_AllocBitmap(int32 width, int32 height)
{
if (width < 2 || height < 2)
return;
delete fGradientBitmap;
fGradientBitmap = new BBitmap(BRect(0, 0, width - 1, height - 1), 0, B_RGB32);
}
BRect
GradientControl::_GradientBitmapRect() const
{
BRect r = Bounds();
r.left += 6.0;
r.top += 2.0;
r.right -= 6.0;
r.bottom -= 14.0;
return r;
}
int32
GradientControl::_StepIndexFor(BPoint where) const
{
int32 index = -1;
BRect r = _GradientBitmapRect();
BRect markerFrame(Bounds());
for (int32 i = 0; BGradient::ColorStop* step
= fGradient->ColorAt(i); i++) {
markerFrame.left = markerFrame.right = r.left
+ (r.Width() * step->offset);
markerFrame.InsetBy(-6.0, 0.0);
if (markerFrame.Contains(where)) {
index = i;
break;
}
}
return index;
}
float
GradientControl::_OffsetFor(BPoint where) const
{
BRect r = _GradientBitmapRect();
float offset = (where.x - r.left) / r.Width();
offset = max_c(0.0, offset);
offset = min_c(1.0, offset);
return offset;
}
void
GradientControl::_UpdateCurrentColor() const
{
if (!fMessage || !fTarget || !fTarget->Looper())
return;
if (BGradient::ColorStop* step = fGradient->ColorAt(fCurrentStepIndex)) {
BMessage message(*fMessage);
store_color_in_message(&message, step->color);
fTarget->Looper()->PostMessage(&message, fTarget);
}
}