/* * Copyright 2006, 2023, Haiku. * Distributed under the terms of the MIT License. * * Authors: * Stephan Aßmus * Zardshard */ #include "GradientControl.h" #include #include #include #include #include #include #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); // handle double click int32 clicks; if (Window()->CurrentMessage()->FindInt32("clicks", &clicks) >= B_OK && clicks >= 2) { if (fDraggingStepIndex < 0) { // create a new offset at the click location that uses // the interpolated color float offset = _OffsetFor(where); // create a clean gradient uint32 width = fGradientBitmap->Bounds().IntegerWidth(); uint8* temp = new uint8[width * 4]; fGradient->MakeGradient((uint32*)temp, width); // get the color at the offset 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) { // start dragging this stop 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); // fall back to inserting a color step if no direct hit on an existing step 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: // remove step 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: // move step to the left x = max_c(r.left, x - 1.0); break; case B_RIGHT_ARROW: // move step to the right x = min_c(r.right, x + 1.0); break; case B_HOME: // move step to the start x = r.left; break; case B_END: // move step to the start x = r.right; break; } update = fGradient->SetOffset(fCurrentStepIndex, (x - r.left) / r.Width()); } break; } case B_UP_ARROW: // previous step fCurrentStepIndex--; if (fCurrentStepIndex < 0) { fCurrentStepIndex = count - 1; } _UpdateCurrentColor(); break; case B_DOWN_ARROW: // next step 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); // background // left of gradient rect BRect lb(updateRect.left, updateRect.top, b.left - 1, b.bottom); if (lb.IsValid()) FillRect(lb, B_SOLID_LOW); // right of gradient rect BRect rb(b.right + 1, updateRect.top, updateRect.right, b.bottom); if (rb.IsValid()) FillRect(rb, B_SOLID_LOW); // bottom of gradient rect 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); } // DrawBitmapAsync(fGradientBitmap, b.LeftTop()); // Sync(); DrawBitmap(fGradientBitmap, b.LeftTop()); // show drop offset 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); } // override in case this is the drop index step if (i == fDropIndex) SetLowColor(255, 0, 0, 255); if (be_control_look != NULL) { // TODO: Drop indication! 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; // fill in top row by gradient uint8* topRow = (uint8*)fGradientBitmap->Bits(); uint32 width = fGradientBitmap->Bounds().IntegerWidth() + 1; fGradient->MakeGradient((uint32*)topRow, width); // flip colors, since they are the wrong endianess // make colors the disabled look // TODO: apply gamma lut 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; } } // copy top row to rest of bitmap 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; } // post process bitmap to underlay it with a pattern // in order to make gradient steps with alpha more visible! 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; // set the CanvasView current color if (BGradient::ColorStop* step = fGradient->ColorAt(fCurrentStepIndex)) { BMessage message(*fMessage); store_color_in_message(&message, step->color); fTarget->Looper()->PostMessage(&message, fTarget); } }