root/src/add-ons/screen_savers/leaves/Leaves.cpp
/*
 * Copyright 2010-2013, Haiku, Inc. All Rights Reserved.
 * Distributed under the terms of the MIT license.
 *
 * Authors:
 *              Deyan Genovski, deangenovski@gmail.com
 *              Geoffry Song, goffrie@gmail.com
 */


#include "Leaves.h"

#include <stdlib.h>
#include <time.h>

#include <AffineTransform.h>
#include <Catalog.h>
#include <GradientLinear.h>
#include <Shape.h>
#include <Slider.h>
#include <TextView.h>


#undef B_TRANSLATION_CONTEXT
#define B_TRANSLATION_CONTEXT "Leaves"


// path data for the leaf shape
static const BPoint kLeafBegin(56.24793f, 15.46287f);
static BPoint kLeafCurves[][3] = {
        { BPoint(61.14, 28.89), BPoint(69.78, 38.25), BPoint(83.48, 44.17) },
        { BPoint(99.46, 37.52), BPoint(113.27, 29.61), BPoint(134.91, 30.86) },
        { BPoint(130.58, 36.53), BPoint(126.74, 42.44), BPoint(123.84, 48.81) },
        { BPoint(131.81, 42.22), BPoint(137.53, 38.33), BPoint(144.37, 33.10) },
        { BPoint(169.17, 23.55), BPoint(198.90, 15.55), BPoint(232.05, 10.51) },
        { BPoint(225.49, 18.37), BPoint(219.31, 28.17), BPoint(217.41, 40.24) },
        { BPoint(227.70, 26.60), BPoint(239.97, 14.63), BPoint(251.43, 8.36) },
        { BPoint(288.89, 9.12), BPoint(322.73, 14.33), BPoint(346.69, 31.67) },
        { BPoint(330.49, 37.85), BPoint(314.36, 44.25), BPoint(299.55, 54.17) },
        { BPoint(292.48, 52.54), BPoint(289.31, 49.70), BPoint(285.62, 47.03) },
        { BPoint(283.73, 54.61), BPoint(284.46, 57.94), BPoint(285.62, 60.60) },
        { BPoint(259.78, 76.14), BPoint(233.24, 90.54), BPoint(202.41, 98.10) },
        { BPoint(194.43, 95.36), BPoint(185.96, 92.39), BPoint(179.63, 88.33) },
        { BPoint(180.15, 94.75), BPoint(182.73, 99.76), BPoint(185.62, 104.53) },
        { BPoint(154.83, 119.46), BPoint(133.21, 118.97), BPoint(125.62, 94.88) },
        { BPoint(124.70, 98.79), BPoint(124.11, 103.67), BPoint(124.19, 110.60) },
        { BPoint(116.42, 111.81), BPoint(85.82, 99.60), BPoint(83.25, 51.96) },
        { BPoint(62.50, 42.57), BPoint(58.12, 33.18), BPoint(50.98, 23.81) } };
static const int kLeafCurveCount = sizeof(kLeafCurves) / sizeof(kLeafCurves[0]);
static const float kLeafWidth = 372.f;
static const float kLeafHeight = 121.f;

// leaf colors
static const rgb_color kColors[][2] = {
        { {255, 114, 0, 255}, {255, 159, 0, 255} },
        { {50, 160, 40, 255}, {125, 210, 32, 255} },
        { {250, 190, 30, 255}, {255, 214, 125, 255} } };
static const int kColorCount = sizeof(kColors) / sizeof(kColors[0]);

// bounds for settings
static const int kMinimumDropRate = 2, kMaximumDropRate = 50;
static const int kMinimumLeafSize = 50, kMaximumLeafSize = 1000;
static const int kMaximumSizeVariation = 200;

// message constants
enum {
        MSG_SET_DROP_RATE               = 'drop',
        MSG_SET_LEAF_SIZE               = 'size',
        MSG_SET_SIZE_VARIATION  = 'svry',
};


extern "C" BScreenSaver*
instantiate_screen_saver(BMessage* msg, image_id image)
{
        return new Leaves(msg, image);
}


Leaves::Leaves(BMessage* archive, image_id id)
        :
        BScreenSaver(archive, id),
        fDropRate(10),
        fLeafSize(150),
        fSizeVariation(50)
{
        if (archive) {
                if (archive->FindInt32("Leaves drop rate", &fDropRate) != B_OK)
                        fDropRate = 10;
                if (archive->FindInt32("Leaves size", &fLeafSize) != B_OK)
                        fLeafSize = 150;
                if (archive->FindInt32("Leaves variation", &fSizeVariation) != B_OK)
                        fSizeVariation = 50;
        }
}


void
Leaves::StartConfig(BView* view)
{
        BRect bounds = view->Bounds();
        bounds.InsetBy(10, 10);
        BRect frame(0, 0, bounds.Width(), 20);

        BSlider* dropRateSlider = new BSlider(frame, "drop rate",
                B_TRANSLATE("Drop rate:"), new BMessage(MSG_SET_DROP_RATE),
                kMinimumDropRate, kMaximumDropRate,     B_BLOCK_THUMB,
                B_FOLLOW_LEFT_RIGHT | B_FOLLOW_BOTTOM);
        dropRateSlider->SetValue(fDropRate);
        dropRateSlider->ResizeToPreferred();
        bounds.bottom -= dropRateSlider->Bounds().Height() * 1.5;
        dropRateSlider->MoveTo(bounds.LeftBottom());
        view->AddChild(dropRateSlider);

        BSlider* leafSizeSlider = new BSlider(frame, "leaf size",
                B_TRANSLATE("Leaf size:"), new BMessage(MSG_SET_LEAF_SIZE),
                kMinimumLeafSize, kMaximumLeafSize,     B_BLOCK_THUMB,
                B_FOLLOW_LEFT_RIGHT | B_FOLLOW_BOTTOM);
        leafSizeSlider->SetValue(fLeafSize);
        leafSizeSlider->ResizeToPreferred();
        bounds.bottom -= leafSizeSlider->Bounds().Height() * 1.5;
        leafSizeSlider->MoveTo(bounds.LeftBottom());
        view->AddChild(leafSizeSlider);

        BSlider* sizeVariationSlider = new BSlider(frame, "variation",
                B_TRANSLATE("Size variation:"), new BMessage(MSG_SET_SIZE_VARIATION),
                0, kMaximumSizeVariation, B_BLOCK_THUMB,
                B_FOLLOW_LEFT_RIGHT | B_FOLLOW_BOTTOM);
        sizeVariationSlider->SetValue(fSizeVariation);
        sizeVariationSlider->ResizeToPreferred();
        bounds.bottom -= sizeVariationSlider->Bounds().Height() * 1.5;
        sizeVariationSlider->MoveTo(bounds.LeftBottom());
        view->AddChild(sizeVariationSlider);

        BTextView* textView = new BTextView(bounds, B_EMPTY_STRING,
                bounds.OffsetToCopy(0., 0.), B_FOLLOW_ALL, B_WILL_DRAW);
        textView->SetViewColor(view->ViewColor());
        BString name = B_TRANSLATE("Leaves");
        BString text = name;
        text << "\n\n";
        text << B_TRANSLATE("by Deyan Genovski, Geoffry Song");
        text << "\n\n";

        textView->Insert(text.String());
        textView->SetStylable(true);
        textView->SetFontAndColor(0, name.Length(), be_bold_font);
        textView->MakeEditable(false);
        view->AddChild(textView);

        BWindow* window = view->Window();
        if (window) window->AddHandler(this);

        dropRateSlider->SetTarget(this);
        leafSizeSlider->SetTarget(this);
        sizeVariationSlider->SetTarget(this);
}


status_t
Leaves::StartSaver(BView* view, bool preview)
{
        SetTickSize(10000000 / fDropRate);
        srand48(time(NULL));

        view->SetLineMode(B_ROUND_CAP, B_ROUND_JOIN);

        return B_OK;
}


status_t
Leaves::SaveState(BMessage* into) const
{
        status_t status;
        if ((status = into->AddInt32("Leaves drop rate", fDropRate)) != B_OK)
                return status;
        if ((status = into->AddInt32("Leaves size", fLeafSize)) != B_OK)
                return status;
        if ((status = into->AddInt32("Leaves variation", fSizeVariation)) != B_OK)
                return status;
        return B_OK;
}


void
Leaves::MessageReceived(BMessage* message)
{
        switch (message->what) {
                case MSG_SET_DROP_RATE:
                {
                        int32 dropRate;
                        if (message->FindInt32("be:value", &dropRate) == B_OK)
                                fDropRate = dropRate;
                        break;
                }

                case MSG_SET_LEAF_SIZE:
                {
                        int32 leafSize;
                        if (message->FindInt32("be:value", &leafSize) == B_OK)
                                fLeafSize = leafSize;
                        break;
                }

                case MSG_SET_SIZE_VARIATION:
                {
                        int32 sizeVariation;
                        if (message->FindInt32("be:value", &sizeVariation) == B_OK)
                                fSizeVariation = sizeVariation;
                        break;
                }

                default:
                        BHandler::MessageReceived(message);
        }
}


void
Leaves::Draw(BView* view, int32 frame)
{
        float scale = fLeafSize / kLeafWidth / (kMaximumLeafSize * 2);
        scale *= view->Bounds().Width();
        scale += scale * drand48() * fSizeVariation / 100.;

        BAffineTransform transform;
        transform.TranslateBy(-kLeafWidth / 2, -kLeafHeight / 2);
                // draw the leaf centered on the point
        transform.RotateBy(drand48() * 2. * M_PI);
        if ((rand() & 64) == 0) transform.ScaleBy(-1., 1.);
                // flip half of the time
        transform.ScaleBy(scale);
        transform.TranslateBy(_RandomPoint(view->Bounds()));

        BPoint center = transform.Apply(BPoint(kLeafWidth / 2, kLeafHeight / 2));
        BPoint gradientOffset = BPoint(60 * scale, 80 * scale);
        BGradientLinear gradient(center - gradientOffset, center + gradientOffset);
        int color = (rand() / 7) % kColorCount;
        gradient.AddColor(kColors[color][0], 0.f);
        gradient.AddColor(kColors[color][1], 255.f);

        BShape leafShape;
        leafShape.MoveTo(transform.Apply(kLeafBegin));
        for (int i = 0; i < kLeafCurveCount; ++i) {
                BPoint control[3];
                for (int j = 0; j < 3; ++j)
                        control[j] = transform.Apply(kLeafCurves[i][j]);
                leafShape.BezierTo(control);
        }
        leafShape.Close();

        view->PushState();
        view->SetDrawingMode(B_OP_ALPHA);
        view->SetHighColor(0, 0, 0, 50);
        for (int i = 2; i >= 0; --i) {
                view->SetOrigin(i * 0.1, i * 0.3);
                view->SetPenSize(i * 2);
                view->StrokeShape(&leafShape);
        }
        view->PopState();
        view->FillShape(&leafShape, gradient);
}


inline BPoint
Leaves::_RandomPoint(const BRect& bound)
{
        return BPoint(drand48() * (bound.right - bound.left) + bound.left,
                drand48() * (bound.bottom - bound.top) + bound.top);
}