* Copyright 2025, Haiku, Inc. All rights reserved.
* Distributed under the terms of the MIT license.
*
* Authors:
* Pawan Yerramilli <me@pawanyerramilli.com>
*/
#include "SelectAreaView.h"
#include <Application.h>
#include <Bitmap.h>
#include <ControlLook.h>
#include <Screen.h>
#include <Window.h>
#include "Cursor.h"
#include "GraphicsDefs.h"
#include "InterfaceDefs.h"
#include "Point.h"
#include "Rect.h"
#include "Utility.h"
SelectAreaView::SelectAreaView(BBitmap* frame)
:
BView(BScreen().Frame(), "", B_FOLLOW_NONE, B_WILL_DRAW | B_NAVIGABLE),
fScreenShot(frame),
fCursor(BCursor(B_CURSOR_ID_CROSS_HAIR)),
fIsCurrentlyDragging(false),
fStartCorner(B_ORIGIN),
fEndCorner(B_ORIGIN),
fResizable(false),
fCurrentHandle(DRAG_NO_HANDLE),
fMoveDelta(-1, -1)
{
}
void
SelectAreaView::AttachedToWindow()
{
MakeFocus(true);
BBitmap* darkenedShot = new BBitmap(Bounds(), fScreenShot->ColorSpace(), true);
BView* darkenedView = new BView(darkenedShot->Bounds(), "", B_FOLLOW_NONE, 0);
darkenedShot->AddChild(darkenedView);
darkenedShot->Lock();
darkenedView->DrawBitmap(fScreenShot);
darkenedView->SetDrawingMode(B_OP_ALPHA);
if (ui_color(B_PANEL_BACKGROUND_COLOR).IsDark()) {
darkenedView->SetHighColor(255, 255, 255, 128);
SetHighColor(255, 255, 255, 255);
} else {
darkenedView->SetHighColor(0, 0, 0, 128);
SetHighColor(0, 0, 0, 0);
}
darkenedView->FillRect(Bounds());
darkenedView->Sync();
SetViewBitmap(darkenedShot);
darkenedShot->RemoveChild(darkenedView);
delete darkenedView;
delete darkenedShot;
}
void
SelectAreaView::Draw(BRect updateRect)
{
BRect frame = _CurrentFrame();
SetViewCursor(&fCursor);
DrawBitmap(fScreenShot, frame, frame);
rgb_color currentLow = LowColor();
SetLowColor(255, 0, 255, 255);
StrokeRect(frame, B_MIXED_COLORS);
if (fResizable && frame.Width() > 20 && frame.Height() > 20) {
FillRect(_GetHandle(DRAG_TOP_LEFT), B_MIXED_COLORS);
FillRect(_GetHandle(DRAG_TOP_RIGHT), B_MIXED_COLORS);
FillRect(_GetHandle(DRAG_BOTTOM_LEFT), B_MIXED_COLORS);
FillRect(_GetHandle(DRAG_BOTTOM_RIGHT), B_MIXED_COLORS);
}
SetLowColor(currentLow);
}
void
SelectAreaView::MessageReceived(BMessage* message)
{
int32 clicks = 0;
if (message->what == B_MOUSE_DOWN && message->FindInt32("clicks", &clicks) == B_OK
&& clicks == 2) {
_SaveSelection();
} else {
BView::MessageReceived(message);
}
}
void
SelectAreaView::KeyDown(const char* bytes, int32 numBytes)
{
if (bytes[0] == B_ESCAPE) {
BMessage message(SS_SELECT_AREA_FRAME);
message.AddRect("selectAreaFrame", BRect(0, 0, -1, -1));
be_app->PostMessage(&message);
Window()->Quit();
} else if (bytes[0] == B_ENTER)
_SaveSelection();
}
void
SelectAreaView::MouseDown(BPoint point)
{
BPoint mouseLoc;
uint32 buttons;
GetMouse(&mouseLoc, &buttons);
uint32 shiftDown = modifiers() & B_SHIFT_KEY;
Handle handle = _FindSelectedHandle(point);
if (fStartCorner == fEndCorner) {
fResizable = (buttons & B_SECONDARY_MOUSE_BUTTON) || shiftDown;
fIsCurrentlyDragging = true;
fStartCorner = fEndCorner = point;
} else if (fResizable && handle != DRAG_NO_HANDLE) {
fCurrentHandle = handle;
} else if (fResizable && _CurrentFrame().Contains(point)) {
fMoveDelta = point;
fCursor = B_CURSOR_ID_GRABBING;
}
Invalidate();
}
void
SelectAreaView::MouseMoved(BPoint point, uint32 transit, const BMessage* message)
{
if (fIsCurrentlyDragging && point != fEndCorner)
fEndCorner = point;
Handle hoveredHandle = fCurrentHandle == DRAG_NO_HANDLE ?
_FindSelectedHandle(point) : fCurrentHandle;
if (fResizable && (hoveredHandle != DRAG_NO_HANDLE)) {
switch(hoveredHandle) {
case DRAG_TOP_LEFT:
fCursor = B_CURSOR_ID_RESIZE_NORTH_WEST;
break;
case DRAG_TOP_RIGHT:
fCursor = B_CURSOR_ID_RESIZE_NORTH_EAST;
break;
case DRAG_BOTTOM_LEFT:
fCursor = B_CURSOR_ID_RESIZE_SOUTH_WEST;
break;
case DRAG_BOTTOM_RIGHT:
fCursor = B_CURSOR_ID_RESIZE_SOUTH_EAST;
default:
break;
}
} else if (fResizable && !_CurrentFrame().Contains(point))
fCursor = B_CURSOR_ID_SYSTEM_DEFAULT;
else if (fMoveDelta != BPoint(-1, -1))
fCursor = B_CURSOR_ID_GRABBING;
else if (fResizable && _CurrentFrame().Contains(point))
fCursor = B_CURSOR_ID_GRAB;
else
fCursor = B_CURSOR_ID_CROSS_HAIR;
if (fCurrentHandle != DRAG_NO_HANDLE) {
switch(fCurrentHandle) {
case DRAG_TOP_LEFT:
fStartCorner = point;
break;
case DRAG_TOP_RIGHT:
fStartCorner.y = point.y;
fEndCorner.x = point.x;
break;
case DRAG_BOTTOM_LEFT:
fStartCorner.x = point.x;
fEndCorner.y = point.y;
break;
case DRAG_BOTTOM_RIGHT:
fEndCorner = point;
default:
break;
}
}
if (fMoveDelta != BPoint(-1, -1)) {
fStartCorner += (point - fMoveDelta);
fEndCorner += (point - fMoveDelta);
fStartCorner.ConstrainTo(Window()->Frame());
fEndCorner.ConstrainTo(Window()->Frame());
fMoveDelta = point;
}
Invalidate();
}
void
SelectAreaView::MouseUp(BPoint point)
{
if (!fResizable && fIsCurrentlyDragging && fStartCorner != fEndCorner) {
_SaveSelection();
} else if (fResizable) {
fIsCurrentlyDragging = false;
BRect frame = _CurrentFrame();
fStartCorner = frame.LeftTop();
fEndCorner = frame.RightBottom();
fCurrentHandle = DRAG_NO_HANDLE;
if (fMoveDelta != BPoint(-1, -1))
fCursor = B_CURSOR_ID_GRAB;
fMoveDelta = BPoint(-1, -1);
Invalidate();
}
}
void
SelectAreaView::_SaveSelection()
{
BMessage message(SS_SELECT_AREA_FRAME);
message.AddRect("selectAreaFrame", _CurrentFrame());
be_app->PostMessage(&message);
Window()->Quit();
}
BRect
SelectAreaView::_CurrentFrame()
{
BPoint topLeft = BPoint(min_c(fStartCorner.x, fEndCorner.x),
min_c(fStartCorner.y, fEndCorner.y));
BPoint bottomRight = BPoint(max_c(fStartCorner.x, fEndCorner.x),
max_c(fStartCorner.y, fEndCorner.y));
return BRect(topLeft, bottomRight);
}
Handle
SelectAreaView::_FindSelectedHandle(BPoint point)
{
for (int i = DRAG_TOP_LEFT; i < DRAG_NO_HANDLE; i++) {
if (_GetHandle((Handle)i).Contains(point))
return (Handle)i;
}
return DRAG_NO_HANDLE;
}
BRect
SelectAreaView::_GetHandle(Handle handle)
{
BRect frame = _CurrentFrame();
int size = be_control_look->ComposeIconSize(8).IntegerWidth();
switch (handle) {
case DRAG_TOP_LEFT:
return BRect(frame.LeftTop(), frame.LeftTop() + BPoint(size, size));
case DRAG_TOP_RIGHT:
return BRect(frame.RightTop() - BPoint(size, 0), frame.RightTop() + BPoint(0, size));
case DRAG_BOTTOM_LEFT:
return BRect(frame.LeftBottom() - BPoint(0, size), frame.LeftBottom() + BPoint(size, 0));
case DRAG_BOTTOM_RIGHT:
return BRect(frame.RightBottom() - BPoint(size, size), frame.RightBottom());
default:
return BRect(0, 0, -1, -1);
}
}