CanadianMachines / CanadianExperienceLib / ViewTimeline.cpp
ViewTimeline.cpp
Raw
/**
 * @file ViewTimeline.cpp
 * @author Charles B. Owen
 */

#include "pch.h"

#include <wx/dcbuffer.h>
#include <wx/xrc/xmlres.h>
#include <sstream>

#include "ViewTimeline.h"
#include "TimelineDlg.h"
#include "MachineDlg.h"
#include "Picture.h"
#include "Actor.h"


using namespace std;

/// Y location for the top of a tick mark
const int TickTop = 15;

/// The spacing between ticks in the timeline
const int TickSpacing = 4;

/// The length of a short tick mark
const int TickShort = 10;

/// The length of a long tick mark
const int TickLong = 20;

/// Size of the tick mark labels
const int TickFontSize = 15;

/// Space to the left of the scale
const int BorderLeft = 10;

/// Space to the right of the scale
const int BorderRight = 10;

/// Filename for the pointer image
const std::wstring PointerImageFile = L"/pointer.png";


/**
 * Constructor
 * @param parent The main wxFrame object
 * @param imagesDir The directory containing the program images
 */
ViewTimeline::ViewTimeline(wxFrame* parent, std::wstring imagesDir) :
    wxScrolledCanvas(parent,
            wxID_ANY,
            wxDefaultPosition,
            wxSize(100, Height),
            wxBORDER_SIMPLE)
{
    SetBackgroundStyle(wxBG_STYLE_PAINT);

    mPointerImage = make_unique<wxImage>(imagesDir + PointerImageFile, wxBITMAP_TYPE_ANY);

    Bind(wxEVT_PAINT, &ViewTimeline::OnPaint, this);
    Bind(wxEVT_LEFT_DOWN, &ViewTimeline::OnLeftDown, this);
    Bind(wxEVT_LEFT_UP, &ViewTimeline::OnLeftUp, this);
    Bind(wxEVT_MOTION, &ViewTimeline::OnMouseMove, this);
    Bind(wxEVT_TIMER, &ViewTimeline::OnTimer, this);

    parent->Bind(wxEVT_COMMAND_MENU_SELECTED, &ViewTimeline::OnFileSaveAs, this, wxID_SAVEAS);
    parent->Bind(wxEVT_COMMAND_MENU_SELECTED, &ViewTimeline::OnFileOpen, this, wxID_OPEN);
    parent->Bind(wxEVT_COMMAND_MENU_SELECTED, &ViewTimeline::OnEditTimelineProperties, this, XRCID("EditTimelineProperties"));
    parent->Bind(wxEVT_COMMAND_MENU_SELECTED, &ViewTimeline::OnEditSetKeyframe, this, XRCID("EditSetKeyframe"));
    parent->Bind(wxEVT_COMMAND_MENU_SELECTED, &ViewTimeline::OnEditDeleteKeyframe, this, XRCID("EditDeleteKeyframe"));
    parent->Bind(wxEVT_COMMAND_MENU_SELECTED, &ViewTimeline::OnPlayPlay, this, XRCID("PlayPlay"));
    parent->Bind(wxEVT_COMMAND_MENU_SELECTED, &ViewTimeline::OnPlayStop, this, XRCID("PlayStop"));
    parent->Bind(wxEVT_COMMAND_MENU_SELECTED, &ViewTimeline::OnPlayPlayFromBeginning, this, XRCID("PlayPlayFromBeginning"));
	parent->Bind(wxEVT_COMMAND_MENU_SELECTED, &ViewTimeline::OnEditMachineAppearances, this, XRCID("EditMachineAppearances"));

	mTimer.SetOwner(this);
    mStopWatch.Start(0);
    mStopWatch.Pause();
}

/**
 * Force an update of this window when the picture changes.
 */
void ViewTimeline::UpdateObserver()
{
    Refresh();
    Update();
}

/**
 * Paint event, draws the window.
 * @param event Paint event object
 */
void ViewTimeline::OnPaint(wxPaintEvent& event)
{
    // Get the timeline
    Timeline *timeline = GetPicture()->GetTimeline();
    int sizeTotal = timeline->GetNumFrames() * TickSpacing + BorderLeft + BorderRight, WindowHeight;
    SetVirtualSize(sizeTotal, 0);
    SetScrollRate(1, 0);

    wxAutoBufferedPaintDC dc(this);
    DoPrepareDC(dc);

    wxBrush background(*wxWHITE);
    dc.SetBackground(background);
    dc.Clear();

    // Create a graphics context
    auto graphics = std::shared_ptr<wxGraphicsContext>(wxGraphicsContext::Create( dc ));

    if(mPointerBitmap.IsNull())
    {
        mPointerBitmap = graphics->CreateBitmapFromImage(*mPointerImage);
    }

    auto rect = GetClientRect();
    int hit = rect.GetHeight();
    int wid = rect.GetWidth();

    wxFont font(wxSize(0, TickFontSize),
            wxFONTFAMILY_SWISS,
            wxFONTSTYLE_NORMAL,
            wxFONTWEIGHT_NORMAL);
    graphics->SetFont(font, *wxBLACK);
    graphics->SetPen(*wxBLACK_PEN);

    int top = TickTop;


    for (int tickNum = 0; tickNum <= timeline->GetNumFrames(); tickNum++)
    {
        int x = BorderLeft + tickNum * TickSpacing;
        int bottom = top + TickShort;

        bool onSecond = (tickNum % timeline->GetFrameRate()) == 0;
        if (onSecond)
        {
            bottom = top + TickLong;

            // Convert the tick number to seconds in a string
            std::wstringstream str;
            str << tickNum / timeline->GetFrameRate();
            std::wstring wstr = str.str();

            double w, h;
            graphics->GetTextExtent(wstr, &w, &h);

            graphics->DrawText(wstr, x - w / 2, bottom + 5);
        }

        graphics->StrokeLine(x, bottom, x, top);
    }

    //
    // Draw the pointer
    //
    int pw = mPointerImage->GetWidth();
    int ph = mPointerImage->GetHeight();
    int x = BorderLeft + (int)(timeline->GetCurrentTime() * timeline->GetFrameRate() * TickSpacing);
    graphics->DrawBitmap(mPointerBitmap,
            x - pw / 2, top,
            pw, ph
    );
}

/**
 * Handle the left mouse button down event
 * @param event
 */
void ViewTimeline::OnLeftDown(wxMouseEvent &event)
{
    auto click = CalcUnscrolledPosition(event.GetPosition());

    int x = click.x;

    // Get the timeline
    Timeline *timeline = GetPicture()->GetTimeline();
    int pointerX = (int)(timeline->GetCurrentTime() * timeline->GetFrameRate() * TickSpacing + BorderLeft);

    mMovingPointer = x >= pointerX - (int)mPointerImage->GetWidth() / 2 && x <= pointerX + (int)mPointerImage->GetWidth() / 2;
}

/**
* Handle the left mouse button up event
* @param event
*/
void ViewTimeline::OnLeftUp(wxMouseEvent &event)
{
    OnMouseMove(event);
}

/**
* Handle the mouse move event
* @param event
*/
void ViewTimeline::OnMouseMove(wxMouseEvent &event)
{
    auto click = CalcUnscrolledPosition(event.GetPosition());

    Timeline *timeline = GetPicture()->GetTimeline();

    if (mMovingPointer && event.LeftIsDown())
    {
		double time = (double)(click.x - BorderLeft) / (timeline->GetFrameRate() * TickSpacing);
        if (time < 0)
        {
            time = 0;
        }
        else if (time > timeline->GetDuration())
        {
            time = timeline->GetDuration();
        }

        GetPicture()->SetAnimationTime(time);
    }
    else
    {
        mMovingPointer = false;
    }

}

/**
 * Handle an Edit>Timeline Properties... menu option
 * @param event The menu event
 */
void ViewTimeline::OnEditTimelineProperties(wxCommandEvent& event)
{
    TimelineDlg dlg(this->GetParent(), GetPicture()->GetTimeline());
    if(dlg.ShowModal() == wxID_OK)
    {
        GetPicture()->UpdateObservers();
    }
}

/**
 * Handle an Edit>Machine Appearances... menu option
 * @param event The menu event
 */
void ViewTimeline::OnEditMachineAppearances(wxCommandEvent& event)
{
	MachineDlg dlg(this->GetParent(),GetPicture()->GetMachineDrawable1(),GetPicture()->GetMachineDrawable2());
	if(dlg.ShowModal() == wxID_OK)
	{
		GetPicture()->UpdateObservers();
	}
}

/**
 * Handle the Edit>Set Keyframe menu option
 * @param event The menu event
 */
void ViewTimeline::OnEditSetKeyframe(wxCommandEvent& event)
{
    auto picture = GetPicture();
    for (auto actor : *picture)
    {
        actor->SetKeyframe();
    }
}

/**
 * Handle the Edit>Delete Keyframe menu option
 * @param event The menu event
 */
void ViewTimeline::OnEditDeleteKeyframe(wxCommandEvent& event)
{
    auto picture = GetPicture();

    picture->GetTimeline()->ClearKeyframe();
    picture->SetAnimationTime(picture->GetAnimationTime());
}

/**
 * Handle a Play>Play menu option
 * @param event Menu event
 */
void ViewTimeline::OnPlayPlay(wxCommandEvent& event)
{
    if(mPlaying)
    {
        // If already playing
        return;
    }

    auto timeline = GetPicture()->GetTimeline();

    auto frameRate = timeline->GetFrameRate();
    auto time = timeline->GetCurrentTime();

    mStopWatch.Start(lround(time * 1000));
    mTimer.Start(1000 / frameRate);
}

/**
 * Handle a Play>Play from Beginning menu option
 * @param event Menu event
 */
void ViewTimeline::OnPlayPlayFromBeginning(wxCommandEvent& event)
{
    if(mPlaying)
    {
        Stop();
    }

    GetPicture()->SetAnimationTime(0);

    auto timeline = GetPicture()->GetTimeline();

    auto frameRate = timeline->GetFrameRate();

    mStopWatch.Start(0);
    mTimer.Start(1000 / frameRate);
}

/**
 * Handle a Stop button press
 * @param event Button event
 */
void ViewTimeline::OnPlayStop(wxCommandEvent& event)
{
    Stop();
}


/**
 * Handle timer events
 * @param event timer event
 */
void ViewTimeline::OnTimer(wxTimerEvent& event)
{
    auto timeline = GetPicture()->GetTimeline();

    auto newTime = mStopWatch.Time() / 1000.0;
    auto frameRate = timeline->GetFrameRate();

    int frame = (int)lround(newTime * frameRate);
    if(frame >= timeline->GetNumFrames())
    {
        frame = timeline->GetNumFrames();
        Stop();
    }

    GetPicture()->SetAnimationTime(newTime);
}


/**
 * Stop any playback animation
 */
void ViewTimeline::Stop()
{
    mPlaying = false;
    mTimer.Stop();
    mStopWatch.Pause();
}


/**
 * File>Save As menu handler
 * @param event Menu event
 */
void ViewTimeline::OnFileSaveAs(wxCommandEvent& event)
{
    wxFileDialog saveFileDialog(this, _("Save Animation file"), "", "",
            "Animation Files (*.anim)|*.anim", wxFD_SAVE|wxFD_OVERWRITE_PROMPT);
    if (saveFileDialog.ShowModal() == wxID_CANCEL)
    {
        return;
    }

    auto filename = saveFileDialog.GetPath();
    GetPicture()->Save(filename);
}

/**
 * File>Open menu handler
 * @param event Menu event
 */
void ViewTimeline::OnFileOpen(wxCommandEvent& event)
{
    wxFileDialog loadFileDialog(this, _("Load Animation file"), "", "",
            "Animation Files (*.anim)|*.anim", wxFD_OPEN);
    if (loadFileDialog.ShowModal() == wxID_CANCEL)
    {
        return;
    }

    auto filename = loadFileDialog.GetPath();
    GetPicture()->Load(filename);
    Refresh();
}