root/src/add-ons/screen_savers/icons/IconsSaver.cpp
/*
 * Copyright 2009 Vincent Duvert, vincent.duvert@free.fr
 * Copyright 2014 Haiku, Inc. All rights reserved.
 *
 * Distributed under the terms of the MIT License.
 *
 * Authors:
 *              Vincent Duvert, vincent.duvert@free.fr
 *              John Scipione, jscipione@gmail.com
 */


#include "IconsSaver.h"

#include <stdlib.h>

#include <Bitmap.h>
#include <Catalog.h>
#include <DefaultSettingsView.h>
#include <MimeType.h>

#include "IconDisplay.h"
#include "VectorIcon.h"


#undef B_TRANSLATION_CONTEXT
#define B_TRANSLATION_CONTEXT "Screensaver Icons"


#define RAND_BETWEEN(a, b) ((rand() % ((b) - (a) + 1) + (a)))


static const int32 kMaxConcurrentIcons = 15;
static const int32 kMinIconWidthPercentage = 5;
        // percentage of the screen width
static const int32 kMaxIconWidthPercentage = 20;
        // same here
static const int32 kMinIconCount = 20;
static const int32 kMaxIconCount = 384;


const rgb_color kBackgroundColor = ui_color(B_DESKTOP_COLOR);


BScreenSaver* instantiate_screen_saver(BMessage* msg, image_id image)
{
        return new IconsSaver(msg, image);
}


//      #pragma mark - IconsSaver


IconsSaver::IconsSaver(BMessage* archive, image_id image)
        :
        BScreenSaver(archive, image),
        fIcons(NULL),
        fBackBitmap(NULL),
        fBackView(NULL),
        fMinSize(0),
        fMaxSize(0)
{
}


IconsSaver::~IconsSaver()
{
        vector_icon* icon;
        while ((icon = fVectorIcons.RemoveItemAt((int32)0)) != NULL) {
                delete[] icon->data;
                free(icon);
        }
}


status_t
IconsSaver::StartSaver(BView* view, bool /*preview*/)
{
        if (fVectorIcons.CountItems() < kMinIconCount)
                _GetVectorIcons();

        srand(system_time() % INT_MAX);

        BRect screenRect(0, 0, view->Frame().Width(), view->Frame().Height());
        fBackBitmap = new BBitmap(screenRect, B_RGBA32, true);
        if (!fBackBitmap->IsValid())
                return B_NO_MEMORY;

        fBackView = new BView(screenRect, "back view", 0, 0);
        if (fBackView == NULL)
                return B_NO_MEMORY;

        fBackView->SetViewColor(kBackgroundColor);
        fBackView->SetHighColor(kBackgroundColor);

        fBackBitmap->AddChild(fBackView);
        if (fBackBitmap->Lock()) {
                fBackView->FillRect(fBackView->Frame());
                fBackView->SetDrawingMode(B_OP_ALPHA);
                fBackView->SetBlendingMode(B_CONSTANT_ALPHA, B_ALPHA_OVERLAY);
                fBackView->Sync();
                fBackBitmap->Unlock();
        }

        fIcons = new IconDisplay[kMaxConcurrentIcons];

        fMaxSize = (screenRect.IntegerWidth() * kMaxIconWidthPercentage) / 100;
        fMinSize = (screenRect.IntegerWidth() * kMinIconWidthPercentage) / 100;
        if (fMaxSize > 255)
                fMaxSize = 255;

        if (fMaxSize > screenRect.IntegerHeight())
                fMaxSize = screenRect.IntegerHeight();

        if (fMinSize > fMaxSize)
                fMinSize = fMaxSize;

        return B_OK;
}


void
IconsSaver::StopSaver()
{
        delete[] fIcons;
        fIcons = NULL;
        delete fBackBitmap;
        fBackBitmap = NULL;
}


void
IconsSaver::Draw(BView* view, int32 frame)
{
        static int32 previousFrame = 0;

        // update drawing
        if (fBackBitmap->Lock()) {
                for (uint8 i = 0 ; i < kMaxConcurrentIcons ; i++)
                        fIcons[i].ClearOn(fBackView);

                int32 delta = frame - previousFrame;
                for (uint8 i = 0 ; i < kMaxConcurrentIcons ; i++)
                        fIcons[i].DrawOn(fBackView, delta);

                fBackView->Sync();
                fBackBitmap->Unlock();
        }

        // sync the view with the back buffer
        view->DrawBitmap(fBackBitmap);
        previousFrame = frame;

        if (fVectorIcons.CountItems() < kMinIconCount)
                return;

        // restart one icon
        for (uint8 i = 0 ; i < kMaxConcurrentIcons ; i++) {
                if (!fIcons[i].IsRunning()) {
                        uint16 size = RAND_BETWEEN(fMinSize, fMaxSize);
                        uint16 maxX = view->Frame().IntegerWidth() - size;
                        uint16 maxY = view->Frame().IntegerHeight() - size;

                        BRect iconFrame(0, 0, size, size);
                        iconFrame.OffsetTo(RAND_BETWEEN(0, maxX), RAND_BETWEEN(0, maxY));

                        // Check that the icon doesn't overlap with others
                        for (uint8 j = 0 ; j < kMaxConcurrentIcons ; j++) {
                                if (fIcons[j].IsRunning() &&
                                        iconFrame.Intersects(fIcons[j].GetFrame()))
                                        return;
                        }

                        int32 index = RAND_BETWEEN(0, fVectorIcons.CountItems() - 1);
                        fIcons[i].Run(fVectorIcons.ItemAt(index), iconFrame);
                        return;
                }
        }
}


void
IconsSaver::StartConfig(BView* view)
{
        BPrivate::BuildDefaultSettingsView(view, "Icons",
                B_TRANSLATE("by Vincent Duvert"));
}


//      #pragma mark - IconsSaver private methods


void
IconsSaver::_GetVectorIcons()
{
        // Load vector icons from the MIME type database
        BMessage types;
        if (BMimeType::GetInstalledTypes(&types) != B_OK)
                return;

        const char* type;
        for (int32 i = 0; types.FindString("types", i, &type) == B_OK; i++) {
                BMimeType mimeType(type);
                if (mimeType.InitCheck() != B_OK)
                        continue;

                uint8* data;
                size_t size;

                if (mimeType.GetIcon(&data, &size) != B_OK) {
                        // didn't find an icon
                        continue;
                }

                vector_icon* icon = (vector_icon*)malloc(sizeof(vector_icon));
                if (icon == NULL) {
                        // ran out of memory, delete the icon data
                        delete[] data;
                        continue;
                }

                icon->data = data;
                icon->size = size;

                // found a vector icon, add it to the list
                fVectorIcons.AddItem(icon);
                if (fVectorIcons.CountItems() >= kMaxIconCount) {
                        // this is enough to choose from, stop eating memory...
                        return;
                }
        }
}