The Walking Dead Screensaver - Part 2

 

Okay, so we need to wrap this up and put a bow on it. To do that, we'll need to implement a ScreenSaver class and construct a minimal application with it. So first thing is first, here is the class definition (ScreenSaver.hpp):


#ifndef SCREENSAVER_HPP
#define SCREENSAVER_HPP

#include <SFML/Graphics/Drawable.hpp>

#include <Carousel.hpp>
#include <DataTables.hpp>

class ScreenSaver : public sf::Drawable
{
    public:
        ScreenSaver(sf::RenderWindow& window, sf::Vector2f scalingFactor, sf::Uint32 type, float effectDuration);
        ~ScreenSaver();

        void update(sf::Time dt);
        bool detectMotion(sf::Event e);

        void draw (sf::RenderTarget& target, sf::RenderStates states) const;

    private:
        std::vector<ImageData> m_imageData;
        Carousel m_carousel;

        sf::Time m_duration;
        float m_effectDuration;

        sf::Vector2i m_initPos;
        sf::Vector2i m_movement;
        sf::Vector2i m_fudgefactor;
};

#endif // SCREENSAVER_HPP

There really isn't much to it. There is a vector to store the image data (paths to images and the sprites themselves), some housekeeping variables for supplying information to the Carousel instance, and methods for updating and killing the application when the end-user moves their mouse. The three vectors at the bottom store information useful for detecting mouse motion and determining if the mouse has in fact moved. Here is the implementation (ScreenSaver.cpp):


#include <ScreenSaver.hpp>

#include <SFML/Graphics/RenderTarget.hpp>

ScreenSaver::ScreenSaver(sf::RenderWindow& window, sf::Vector2f scalingFactor, sf::Uint32 type, float effectDuration)
: m_imageData(initializeImageData())
, m_carousel(window, scalingFactor, type, effectDuration)
, m_duration(sf::Time::Zero)
, m_effectDuration(effectDuration)
, m_initPos(400,300)
, m_movement(0,0)
, m_fudgefactor(sf::Vector2i(4,4))
{
    //center the mouse
    sf::Mouse::setPosition(sf::Vector2i(400,300));

    for(auto& i : m_imageData)
    {
        m_carousel.addItem(i.m_sprite);
    }
}

ScreenSaver::~ScreenSaver()
{

}

void ScreenSaver::update(sf::Time dt)
{
    m_carousel.update(dt);

    if(m_duration.asSeconds() >= m_effectDuration)
    {
        m_carousel.autoScroll(CarouselMovement::FORWARD);
        m_duration = sf::Time::Zero;
    }
    else
        m_duration += dt;
}

bool ScreenSaver::detectMotion(sf::Event e)
{
    if(e.type == sf::Event::MouseMoved)
    {
        m_movement = sf::Mouse::getPosition();
        //make sure to use the absolute value of the difference
        //between the initial mouse position and the movement
        //and compare it to the fudge factor
        //if this value is greater exit the screensaver
        if (abs(m_movement.x - m_initPos.x) > m_fudgefactor.x
            || abs(m_movement.y - m_initPos.y) > m_fudgefactor.y)
        {
            return true;
        }
    }

    return false;
}

void ScreenSaver::draw(sf::RenderTarget& target, sf::RenderStates states) const
{
    target.draw(m_carousel);
}

Note the heavy use of initializer lists (c++11 feature) - they are nice for a lot of reasons but the main reason is that it avoids extra calls to your member data default constructors on construction of this class. See stack overflow for an explanation and Bjarne Stroustrup for a description. ;)

The two main methods of this class handle most everything. The ScreenSaver::update(sf::Time dt) method handles when the Carousel moves by calling its autoscroll method every time the user specified value for duration cycles. After the scroll takes place, time is reset and the cycle starts anew.

The ScreenSaver::detectMotion(sf::Event e) method takes an event object and checks for mouse motion. If mouse motion is detected, the app is killed and the user is either directed to their desktop or the login screen depending on how they configure the screensaver options available to them. Mouse motion is derived from the absolute value of the difference between the motion detected and the original position of the mouse. If the difference is greater than the "fudge factor" vector, the app is killed. Now, here is the main entry point (main.cpp):


#include <iostream>

#include <SFML/Graphics.hpp>

#include <ScreenSaver.hpp>

int main()
{
    sf::VideoMode mode = sf::VideoMode::getDesktopMode();
    sf::RenderWindow window(mode, "The Walking Dead", sf::Style::Fullscreen);
    sf::Event e;

    //try to support as many resolutions as possible
    //using a scaling factor for all resources
    sf::Vector2f scaleFactor = sf::Vector2f(window.getSize().x/2560.f, window.getSize().y/1440.f);

    //set up the image carousel
    ScreenSaver myScreenSaver(window, scaleFactor, CarouselType::FADE, 4.f);

    sf::Time timePerFrame = sf::seconds(1.f/60.f);
    sf::Clock clock;
    sf::Time timeElapsed = sf::Time::Zero;

    while(window.isOpen())
    {
        while(window.pollEvent(e))
        {
            if(myScreenSaver.detectMotion(e))
                return 0;
        }

        timeElapsed += clock.restart();
        while (timeElapsed > timePerFrame)
        {
            timeElapsed -= timePerFrame;

            myScreenSaver.update(timePerFrame);
        }

        window.clear();

        window.draw(myScreenSaver);

        window.display();
    }

    return 0;
}

There really wasn't much left to do except create the window, initialize the ScreenSaver instance, call its update method, and draw it. Simple. Now, packaging your application is the last necessary step. I used Inno to make a Windows installer, because it's free. ;) Personally, I just followed the tutorial here, it was pretty easy to make. On my system, it went to the "SysWOW64" folder - there are also screensavers in this directory (typically they are in the System32 folder). I beleive my build was 32-bit, which is why it would be redirected to that folder. If you want a 64-bit build, the source is available. As always, thanks for tuning in.

Download the 32-bit installer here (all code, 32-bit executable, and assets included)

Source on Github