root/src/apps/screenshot/SelectAreaView.cpp
/*
 * 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);
        }
}