root/src/apps/deskbar/InlineScrollView.cpp
/*
 * Copyright 2012, Haiku, Inc.
 * Distributed under the terms of the MIT License.
 *
 * Authors:
 *              Marc Flerackers (mflerackers@androme.be)
 *              Stefano Ceccherini (stefano.ceccherini@gmail.com)
 *              John Scipione (jscipione@gmail.com)
 */


#include "InlineScrollView.h"

#include <ControlLook.h>
#include <Debug.h>
#include <InterfaceDefs.h>
#include <Menu.h>
#include <Point.h>
#include <Screen.h>
#include <Window.h>


const int kDefaultScrollStep = 19;
const int kScrollerDimension = 12;


class ScrollArrow : public BView {
public:
                                                        ScrollArrow(BRect frame);
        virtual                                 ~ScrollArrow();

                        bool                    IsEnabled() const { return fEnabled; };
                        void                    SetEnabled(bool enabled);

private:
                        bool                    fEnabled;
};


class UpScrollArrow : public ScrollArrow {
public:
                                                        UpScrollArrow(BRect frame);
        virtual                                 ~UpScrollArrow();

        virtual void                    Draw(BRect updateRect);
        virtual void                    MouseDown(BPoint where);
};


class DownScrollArrow : public ScrollArrow {
public:
                                                        DownScrollArrow(BRect frame);
        virtual                                 ~DownScrollArrow();

        virtual void                    Draw(BRect updateRect);
        virtual void                    MouseDown(BPoint where);
};


class LeftScrollArrow : public ScrollArrow {
public:
                                                        LeftScrollArrow(BRect frame);
        virtual                                 ~LeftScrollArrow();

        virtual void                    Draw(BRect updateRect);
        virtual void                    MouseDown(BPoint where);
};


class RightScrollArrow : public ScrollArrow {
public:
                                                        RightScrollArrow(BRect frame);
        virtual                                 ~RightScrollArrow();

        virtual void                    Draw(BRect updateRect);
        virtual void                    MouseDown(BPoint where);
};


//      #pragma mark -


ScrollArrow::ScrollArrow(BRect frame)
        :
        BView(frame, "menu scroll arrow", B_FOLLOW_NONE, B_WILL_DRAW),
        fEnabled(false)
{
        SetViewUIColor(B_MENU_BACKGROUND_COLOR);
}


ScrollArrow::~ScrollArrow()
{
}


void
ScrollArrow::SetEnabled(bool enabled)
{
        fEnabled = enabled;
        Invalidate();
}


//      #pragma mark -


UpScrollArrow::UpScrollArrow(BRect frame)
        :
        ScrollArrow(frame)
{
}


UpScrollArrow::~UpScrollArrow()
{
}


void
UpScrollArrow::Draw(BRect updateRect)
{
        SetLowColor(tint_color(ui_color(B_MENU_BACKGROUND_COLOR),
                B_DARKEN_1_TINT));

        if (IsEnabled())
                SetHighColor(0, 0, 0);
        else {
                SetHighColor(tint_color(ui_color(B_MENU_BACKGROUND_COLOR),
                        B_DARKEN_2_TINT));
        }

        FillRect(Bounds(), B_SOLID_LOW);

        float middle = Bounds().right / 2;
        FillTriangle(BPoint(middle, (kScrollerDimension / 2) - 3),
                BPoint(middle + 5, (kScrollerDimension / 2) + 2),
                BPoint(middle - 5, (kScrollerDimension / 2) + 2));
}


void
UpScrollArrow::MouseDown(BPoint where)
{
        if (!IsEnabled())
                return;

        TInlineScrollView* parent = dynamic_cast<TInlineScrollView*>(Parent());
        if (parent == NULL)
                return;

        float smallStep;
        float largeStep;
        parent->GetSteps(&smallStep, &largeStep);

        BMessage* message = Window()->CurrentMessage();
        int32 modifiers = 0;
        message->FindInt32("modifiers", &modifiers);
        // pressing the shift key scrolls faster
        if ((modifiers & B_SHIFT_KEY) != 0)
                parent->ScrollBy(-largeStep);
        else
                parent->ScrollBy(-smallStep);

        snooze(5000);
}


//      #pragma mark -


DownScrollArrow::DownScrollArrow(BRect frame)
        :
        ScrollArrow(frame)
{
}


DownScrollArrow::~DownScrollArrow()
{
}


void
DownScrollArrow::Draw(BRect updateRect)
{
        SetLowColor(tint_color(ui_color(B_MENU_BACKGROUND_COLOR),
                B_DARKEN_1_TINT));

        if (IsEnabled())
                SetHighColor(0, 0, 0);
        else {
                SetHighColor(tint_color(ui_color(B_MENU_BACKGROUND_COLOR),
                        B_DARKEN_2_TINT));
        }

        BRect frame = Bounds();
        FillRect(frame, B_SOLID_LOW);

        float middle = Bounds().right / 2;
        FillTriangle(BPoint(middle, frame.bottom - (kScrollerDimension / 2) + 3),
                BPoint(middle + 5, frame.bottom - (kScrollerDimension / 2) - 2),
                BPoint(middle - 5, frame.bottom - (kScrollerDimension / 2) - 2));
}


void
DownScrollArrow::MouseDown(BPoint where)
{
        if (!IsEnabled())
                return;

        TInlineScrollView* grandparent
                = dynamic_cast<TInlineScrollView*>(Parent()->Parent());
        if (grandparent == NULL)
                return;

        float smallStep;
        float largeStep;
        grandparent->GetSteps(&smallStep, &largeStep);

        BMessage* message = Window()->CurrentMessage();
        int32 modifiers = 0;
        message->FindInt32("modifiers", &modifiers);
        // pressing the shift key scrolls faster
        if ((modifiers & B_SHIFT_KEY) != 0)
                grandparent->ScrollBy(largeStep);
        else
                grandparent->ScrollBy(smallStep);

        snooze(5000);
}


//      #pragma mark -


LeftScrollArrow::LeftScrollArrow(BRect frame)
        :
        ScrollArrow(frame)
{
}


LeftScrollArrow::~LeftScrollArrow()
{
}


void
LeftScrollArrow::Draw(BRect updateRect)
{
        SetLowColor(tint_color(ui_color(B_MENU_BACKGROUND_COLOR), B_DARKEN_1_TINT));

        if (IsEnabled())
                SetHighColor(0, 0, 0);
        else {
                SetHighColor(tint_color(ui_color(B_MENU_BACKGROUND_COLOR),
                        B_DARKEN_2_TINT));
        }

        FillRect(Bounds(), B_SOLID_LOW);

        float middle = Bounds().bottom / 2;
        FillTriangle(BPoint((kScrollerDimension / 2) - 3, middle),
                BPoint((kScrollerDimension / 2) + 2, middle + 5),
                BPoint((kScrollerDimension / 2) + 2, middle - 5));
}


void
LeftScrollArrow::MouseDown(BPoint where)
{
        if (!IsEnabled())
                return;

        TInlineScrollView* parent = dynamic_cast<TInlineScrollView*>(Parent());
        if (parent == NULL)
                return;

        float smallStep;
        float largeStep;
        parent->GetSteps(&smallStep, &largeStep);

        BMessage* message = Window()->CurrentMessage();
        int32 modifiers = 0;
        message->FindInt32("modifiers", &modifiers);
        // pressing the shift key scrolls faster
        if ((modifiers & B_SHIFT_KEY) != 0)
                parent->ScrollBy(-largeStep);
        else
                parent->ScrollBy(-smallStep);

        snooze(5000);
}


//      #pragma mark -


RightScrollArrow::RightScrollArrow(BRect frame)
        :
        ScrollArrow(frame)
{
}


RightScrollArrow::~RightScrollArrow()
{
}


void
RightScrollArrow::Draw(BRect updateRect)
{
        SetLowColor(tint_color(ui_color(B_MENU_BACKGROUND_COLOR), B_DARKEN_1_TINT));

        if (IsEnabled())
                SetHighColor(0, 0, 0);
        else {
                SetHighColor(tint_color(ui_color(B_MENU_BACKGROUND_COLOR),
                        B_DARKEN_2_TINT));
        }

        BRect frame = Bounds();
        FillRect(frame, B_SOLID_LOW);

        float middle = Bounds().bottom / 2;
        FillTriangle(BPoint(kScrollerDimension / 2 + 3, middle),
                BPoint(kScrollerDimension / 2 - 2, middle + 5),
                BPoint(kScrollerDimension / 2 - 2, middle - 5));
}


void
RightScrollArrow::MouseDown(BPoint where)
{
        if (!IsEnabled())
                return;

        TInlineScrollView* grandparent
                = dynamic_cast<TInlineScrollView*>(Parent()->Parent());
        if (grandparent == NULL)
                return;

        float smallStep;
        float largeStep;
        grandparent->GetSteps(&smallStep, &largeStep);

        BMessage* message = Window()->CurrentMessage();
        int32 modifiers = 0;
        message->FindInt32("modifiers", &modifiers);
        // pressing the shift key scrolls faster
        if ((modifiers & B_SHIFT_KEY) != 0)
                grandparent->ScrollBy(largeStep);
        else
                grandparent->ScrollBy(smallStep);

        snooze(5000);
}


//      #pragma mark -


TInlineScrollView::TInlineScrollView(BView* target,
        enum orientation orientation)
        :
        BView(BRect(0, 0, 0, 0), "inline scroll view", B_FOLLOW_NONE, B_WILL_DRAW),
        fTarget(target),
        fBeginScrollArrow(NULL),
        fEndScrollArrow(NULL),
        fScrollStep(kDefaultScrollStep),
        fScrollValue(0),
        fScrollLimit(0),
        fOrientation(orientation)
{
}


TInlineScrollView::~TInlineScrollView()
{
        if (fBeginScrollArrow != NULL) {
                fBeginScrollArrow->RemoveSelf();
                delete fBeginScrollArrow;
                fBeginScrollArrow = NULL;
        }

        if (fEndScrollArrow != NULL) {
                fEndScrollArrow->RemoveSelf();
                delete fEndScrollArrow;
                fEndScrollArrow = NULL;
        }
}


void
TInlineScrollView::AttachedToWindow()
{
        BView::AttachedToWindow();

        if (fTarget == NULL)
                return;

        AddChild(fTarget);
        fTarget->MoveTo(0, 0);
}


void
TInlineScrollView::DetachedFromWindow()
{
        BView::DetachedFromWindow();

        if (fTarget != NULL)
                fTarget->RemoveSelf();

        if (fBeginScrollArrow != NULL)
                fBeginScrollArrow->RemoveSelf();

        if (fEndScrollArrow != NULL)
                fEndScrollArrow->RemoveSelf();
}


void
TInlineScrollView::Draw(BRect updateRect)
{
        BRect frame = Bounds();
        be_control_look->DrawButtonBackground(this, frame, updateRect,
                ui_color(B_MENU_BACKGROUND_COLOR));
}


//      #pragma mark -


void
TInlineScrollView::AttachScrollers()
{
        if (fTarget == NULL)
                return;

        BRect frame = Bounds();

        if (HasScrollers()) {
                if (fOrientation == B_VERTICAL) {
                        fScrollLimit = fTarget->Bounds().Height()
                                - (frame.Height() - 2 * kScrollerDimension);
                } else {
                        fScrollLimit = fTarget->Bounds().Width()
                                - (frame.Width() - 2 * kScrollerDimension);
                }

                if (fScrollValue > fScrollLimit) {
                        // If scroll value is above limit scroll back
                        float delta = fScrollLimit - fScrollValue;
                        if (fOrientation == B_VERTICAL)
                                fTarget->ScrollBy(0, delta);
                        else
                                fTarget->ScrollBy(delta, 0);

                        fScrollValue = fScrollLimit;
                }
                return;
        }

        fTarget->MakeFocus(true);

        if (fOrientation == B_VERTICAL) {
                if (fBeginScrollArrow == NULL) {
                        fBeginScrollArrow = new UpScrollArrow(
                                BRect(frame.left, frame.top, frame.right,
                                        kScrollerDimension - 1));
                        AddChild(fBeginScrollArrow);
                }

                if (fEndScrollArrow == NULL) {
                        fEndScrollArrow = new DownScrollArrow(
                                BRect(0, frame.bottom - 2 * kScrollerDimension + 1, frame.right,
                                        frame.bottom - kScrollerDimension));
                        fTarget->AddChild(fEndScrollArrow);
                }

                fTarget->MoveBy(0, kScrollerDimension);

                fScrollLimit = fTarget->Bounds().Height()
                        - (frame.Height() - 2 * kScrollerDimension);
        } else {
                if (fBeginScrollArrow == NULL) {
                        fBeginScrollArrow = new LeftScrollArrow(
                                BRect(frame.left, frame.top,
                                        frame.left + kScrollerDimension - 1, frame.bottom));
                        AddChild(fBeginScrollArrow);
                }

                if (fEndScrollArrow == NULL) {
                        fEndScrollArrow = new RightScrollArrow(
                                BRect(frame.right - 2 * kScrollerDimension + 1, frame.top,
                                        frame.right, frame.bottom));
                        fTarget->AddChild(fEndScrollArrow);
                }

                fTarget->MoveBy(kScrollerDimension, 0);

                fScrollLimit = fTarget->Bounds().Width()
                        - (frame.Width() - 2 * kScrollerDimension);
        }

        fBeginScrollArrow->SetEnabled(false);
        fEndScrollArrow->SetEnabled(true);

        fScrollValue = 0;
}


void
TInlineScrollView::DetachScrollers()
{
        if (!HasScrollers())
                return;

        if (fEndScrollArrow) {
                fEndScrollArrow->RemoveSelf();
                delete fEndScrollArrow;
                fEndScrollArrow = NULL;
        }

        if (fBeginScrollArrow) {
                fBeginScrollArrow->RemoveSelf();
                delete fBeginScrollArrow;
                fBeginScrollArrow = NULL;
        }

        if (fTarget) {
                // We don't remember the position where the last scrolling
                // ended, so scroll back to the beginning.
                if (fOrientation == B_VERTICAL)
                        fTarget->MoveBy(0, -kScrollerDimension);
                else
                        fTarget->MoveBy(-kScrollerDimension, 0);

                fTarget->ScrollTo(0, 0);
                fScrollValue = 0;
        }
}


bool
TInlineScrollView::HasScrollers() const
{
        return fTarget != NULL && fBeginScrollArrow != NULL
                && fEndScrollArrow != NULL;
}


void
TInlineScrollView::SetSmallStep(float step)
{
        fScrollStep = step;
}


void
TInlineScrollView::GetSteps(float* _smallStep, float* _largeStep) const
{
        if (_smallStep != NULL)
                *_smallStep = fScrollStep;
        if (_largeStep != NULL) {
                *_largeStep = fScrollStep * 3;
        }
}


void
TInlineScrollView::ScrollBy(const float& step)
{
        if (!HasScrollers())
                return;

        if (step > 0) {
                if (fScrollValue == 0)
                        fBeginScrollArrow->SetEnabled(true);

                if (fScrollValue + step >= fScrollLimit) {
                        // If we reached the limit, only scroll to the end
                        if (fOrientation == B_VERTICAL) {
                                fTarget->ScrollBy(0, fScrollLimit - fScrollValue);
                                fEndScrollArrow->MoveBy(0, fScrollLimit - fScrollValue);
                        } else {
                                fTarget->ScrollBy(fScrollLimit - fScrollValue, 0);
                                fEndScrollArrow->MoveBy(fScrollLimit - fScrollValue, 0);
                        }
                        fEndScrollArrow->SetEnabled(false);
                        fScrollValue = fScrollLimit;
                } else {
                        if (fOrientation == B_VERTICAL) {
                                fTarget->ScrollBy(0, step);
                                fEndScrollArrow->MoveBy(0, step);
                        } else {
                                fTarget->ScrollBy(step, 0);
                                fEndScrollArrow->MoveBy(step, 0);
                        }
                        fScrollValue += step;
                }
        } else if (step < 0) {
                if (fScrollValue == fScrollLimit)
                        fEndScrollArrow->SetEnabled(true);

                if (fScrollValue + step <= 0) {
                        if (fOrientation == B_VERTICAL) {
                                fTarget->ScrollBy(0, -fScrollValue);
                                fEndScrollArrow->MoveBy(0, -fScrollValue);
                        } else {
                                fTarget->ScrollBy(-fScrollValue, 0);
                                fEndScrollArrow->MoveBy(-fScrollValue, 0);
                        }
                        fBeginScrollArrow->SetEnabled(false);
                        fScrollValue = 0;
                } else {
                        if (fOrientation == B_VERTICAL) {
                                fTarget->ScrollBy(0, step);
                                fEndScrollArrow->MoveBy(0, step);
                        } else {
                                fTarget->ScrollBy(step, 0);
                                fEndScrollArrow->MoveBy(step, 0);
                        }
                        fScrollValue += step;
                }
        }

        //fTarget->Invalidate();
}