Add new Editor PlayState so we can have several editors playing animations

With this change we share the logic to play animations between the Preview
window and Enter key/play animation.
This commit is contained in:
David Capello 2015-04-06 16:12:28 -03:00
parent 7448db3959
commit a42d9d1c00
13 changed files with 243 additions and 214 deletions

View File

@ -306,6 +306,7 @@ add_library(app-lib
ui/editor/moving_pixels_state.cpp
ui/editor/navigate_state.cpp
ui/editor/pixels_movement.cpp
ui/editor/play_state.cpp
ui/editor/scrolling_state.cpp
ui/editor/select_box_state.cpp
ui/editor/standby_state.cpp

View File

@ -9,146 +9,16 @@
#include "config.h"
#endif
#include "ui/ui.h"
#include "app/app.h"
#include "app/commands/command.h"
#include "app/context.h"
#include "app/context_access.h"
#include "app/handle_anidir.h"
#include "app/loop_tag.h"
#include "app/modules/editors.h"
#include "app/modules/gui.h"
#include "app/modules/palettes.h"
#include "app/pref/preferences.h"
#include "app/settings/settings.h"
#include "app/ui/editor/editor.h"
#include "app/ui/main_window.h"
#include "app/ui/preview_editor.h"
#include "doc/image.h"
#include "doc/palette.h"
#include "doc/sprite.h"
namespace app {
// TODO merge this with PreviewEditor logic and create a new Editor state
using namespace ui;
class PlayAniWindow : public Window {
public:
PlayAniWindow(Context* context, Editor* editor)
: Window(DesktopWindow)
, m_editor(editor)
, m_oldFrame(editor->frame())
, m_oldFlags(m_editor->editorFlags())
, m_doc(editor->document())
, m_docPref(App::instance()->preferences().document(m_doc))
, m_oldOnionskinState(m_docPref.onionskin.active())
, m_playTimer(10)
{
m_editor->setEditorFlags(Editor::kNoneFlag);
// Desactivate the onionskin
m_docPref.onionskin.active(false);
// Clear extras (e.g. pen preview)
m_doc->destroyExtraCel();
setFocusStop(true); // To receive keyboard messages
m_curFrameTick = ui::clock();
m_pingPongForward = true;
m_nextFrameTime = editor->sprite()->frameDuration(editor->frame());
m_playTimer.Tick.connect(&PlayAniWindow::onPlaybackTick, this);
m_playTimer.start();
}
protected:
void onPlaybackTick() {
if (m_nextFrameTime >= 0) {
m_nextFrameTime -= (ui::clock() - m_curFrameTick);
FrameTag* loopTag = get_loop_tag(m_editor->sprite());
while (m_nextFrameTime <= 0) {
frame_t frame = calculate_next_frame(
m_editor->sprite(),
m_editor->frame(), loopTag,
m_pingPongForward);
m_editor->setFrame(frame);
m_nextFrameTime += m_editor->sprite()->frameDuration(frame);
}
invalidate();
m_curFrameTick = ui::clock();
}
}
virtual bool onProcessMessage(Message* msg) override {
switch (msg->type()) {
case kOpenMessage:
ui::set_mouse_cursor(kNoCursor);
break;
case kCloseMessage:
// Restore onionskin flag
m_docPref.onionskin.active(m_oldOnionskinState);
// Restore editor
m_editor->setFrame(m_oldFrame);
m_editor->setEditorFlags(m_oldFlags);
break;
case kMouseUpMessage: {
closeWindow(this);
break;
}
case kKeyDownMessage: {
closeWindow(this);
return true;
}
case kSetCursorMessage:
ui::set_mouse_cursor(kNoCursor);
return true;
}
return Window::onProcessMessage(msg);
}
virtual void onPaint(PaintEvent& ev) override {
Graphics* g = ev.getGraphics();
g->fillRect(gfx::rgba(0, 0, 0), getClientBounds());
Graphics subG(g->getInternalSurface(),
m_editor->getBounds().x + g->getInternalDeltaY(),
m_editor->getBounds().y + g->getInternalDeltaY());
m_editor->drawSpriteUnclippedRect(&subG,
gfx::Rect(0, 0,
m_editor->sprite()->width(),
m_editor->sprite()->height()));
}
private:
Editor* m_editor;
frame_t m_oldFrame;
Editor::EditorFlags m_oldFlags;
Document* m_doc;
DocumentPreferences& m_docPref;
bool m_oldOnionskinState;
bool m_pingPongForward;
int m_nextFrameTime;
int m_curFrameTick;
ui::Timer m_playTimer;
};
class PlayAnimationCommand : public Command {
public:
PlayAnimationCommand();
@ -182,18 +52,14 @@ void PlayAnimationCommand::onExecute(Context* context)
return;
}
// Hide mini editor
PreviewEditorWindow* preview =
App::instance()->getMainWindow()->getPreviewEditor();
bool enabled = (preview ? preview->isPreviewEnabled(): false);
if (enabled)
preview->setPreviewEnabled(false);
ASSERT(current_editor);
if (!current_editor)
return;
PlayAniWindow window(context, current_editor);
window.openWindowInForeground();
if (enabled)
preview->setPreviewEnabled(enabled);
if (current_editor->isPlaying())
current_editor->stop();
else
current_editor->play();
}
Command* CommandFactory::createPlayAnimationCommand()

View File

@ -18,6 +18,14 @@ namespace app {
const char* kLoopTagName = "Loop";
doc::FrameTag* get_animation_tag(const doc::Sprite* sprite, doc::frame_t frame)
{
doc::FrameTag* tag = get_shortest_tag(sprite, frame);
if (!tag)
tag = get_loop_tag(sprite);
return tag;
}
doc::FrameTag* get_shortest_tag(const doc::Sprite* sprite, doc::frame_t frame)
{
const doc::FrameTag* found = nullptr;
@ -33,7 +41,7 @@ doc::FrameTag* get_shortest_tag(const doc::Sprite* sprite, doc::frame_t frame)
return const_cast<doc::FrameTag*>(found);
}
doc::FrameTag* get_loop_tag(doc::Sprite* sprite)
doc::FrameTag* get_loop_tag(const doc::Sprite* sprite)
{
// Get tag with special "Loop" name
for (doc::FrameTag* tag : sprite->frameTags())

View File

@ -18,8 +18,9 @@ namespace doc {
namespace app {
doc::FrameTag* get_animation_tag(const doc::Sprite* sprite, doc::frame_t frame);
doc::FrameTag* get_loop_tag(const doc::Sprite* sprite);
doc::FrameTag* get_shortest_tag(const doc::Sprite* sprite, doc::frame_t frame);
doc::FrameTag* get_loop_tag(doc::Sprite* sprite);
doc::FrameTag* create_loop_tag(doc::frame_t from, doc::frame_t to);
} // namespace app

View File

@ -169,6 +169,7 @@ private:
DocumentView::DocumentView(Document* document, Type type)
: Box(JI_VERTICAL)
, m_type(type)
, m_document(document)
, m_view(new EditorView(type == Normal ? EditorView::CurrentEditorMode:
EditorView::AlwaysSelected))

View File

@ -40,6 +40,8 @@ namespace app {
void getDocumentLocation(DocumentLocation* location) const;
Editor* getEditor() { return m_editor; }
bool isPreview() { return m_type == Preview; }
// TabView implementation
std::string getTabText() override;
TabIcon getTabIcon() override;
@ -67,6 +69,7 @@ namespace app {
bool onProcessMessage(ui::Message* msg) override;
private:
Type m_type;
Document* m_document;
ui::View* m_view;
Editor* m_editor;

View File

@ -33,6 +33,7 @@
#include "app/ui/editor/editor_decorator.h"
#include "app/ui/editor/moving_pixels_state.h"
#include "app/ui/editor/pixels_movement.h"
#include "app/ui/editor/play_state.h"
#include "app/ui/editor/scoped_cursor.h"
#include "app/ui/editor/standby_state.h"
#include "app/ui/main_window.h"
@ -1497,6 +1498,23 @@ void Editor::notifyScrollChanged()
m_observers.notifyScrollChanged(this);
}
void Editor::play()
{
if (!dynamic_cast<PlayState*>(m_state.get()))
setState(EditorStatePtr(new PlayState));
}
void Editor::stop()
{
if (dynamic_cast<PlayState*>(m_state.get()))
backToPreviousState();
}
bool Editor::isPlaying() const
{
return (dynamic_cast<PlayState*>(m_state.get()) != nullptr);
}
// static
ImageBufferPtr Editor::getRenderImageBuffer()
{

View File

@ -188,6 +188,11 @@ namespace app {
// position.
void notifyScrollChanged();
// Animation control
void play();
void stop();
bool isPlaying() const;
// Returns the buffer used to render editor viewports.
// E.g. It can be re-used by PreviewCommand
static ImageBufferPtr getRenderImageBuffer();

View File

@ -0,0 +1,116 @@
// Aseprite
// Copyright (C) 2001-2015 David Capello
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License version 2 as
// published by the Free Software Foundation.
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include "app/ui/editor/play_state.h"
#include "app/handle_anidir.h"
#include "app/loop_tag.h"
#include "app/ui/editor/editor.h"
#include "app/ui/editor/scrolling_state.h"
#include "app/ui_context.h"
#include "ui/system.h"
namespace app {
using namespace ui;
PlayState::PlayState()
: m_playTimer(10)
, m_pingPongForward(true)
, m_nextFrameTime(-1)
{
}
void PlayState::onAfterChangeState(Editor* editor)
{
StateWithWheelBehavior::onAfterChangeState(editor);
m_editor = editor;
m_nextFrameTime = editor->sprite()->frameDuration(editor->frame());
m_curFrameTick = ui::clock();
m_pingPongForward = true;
m_refFrame = editor->frame();
m_playTimer.Tick.connect(&PlayState::onPlaybackTick, this);
m_playTimer.start();
}
EditorState::BeforeChangeAction PlayState::onBeforeChangeState(Editor* editor, EditorState* newState)
{
m_editor->setFrame(m_refFrame);
m_playTimer.stop();
return KeepState;
}
bool PlayState::onMouseDown(Editor* editor, MouseMessage* msg)
{
if (editor->hasCapture())
return true;
// When an editor is clicked the current view is changed.
UIContext* context = UIContext::instance();
context->setActiveView(editor->getDocumentView());
// Start scroll loop
EditorStatePtr newState(new ScrollingState());
editor->setState(newState);
newState->onMouseDown(editor, msg);
return true;
}
bool PlayState::onMouseUp(Editor* editor, MouseMessage* msg)
{
editor->releaseMouse();
return true;
}
bool PlayState::onMouseMove(Editor* editor, MouseMessage* msg)
{
editor->moveDrawingCursor();
editor->updateStatusBar();
return true;
}
bool PlayState::onKeyDown(Editor* editor, KeyMessage* msg)
{
return false;
}
bool PlayState::onKeyUp(Editor* editor, KeyMessage* msg)
{
return false;
}
void PlayState::onPlaybackTick()
{
if (m_nextFrameTime < 0)
return;
m_nextFrameTime -= (ui::clock() - m_curFrameTick);
doc::Sprite* sprite = m_editor->sprite();
doc::FrameTag* tag = get_animation_tag(sprite, m_refFrame);
while (m_nextFrameTime <= 0) {
doc::frame_t frame = calculate_next_frame(
sprite,
m_editor->frame(), tag,
m_pingPongForward);
m_editor->setFrame(frame);
m_nextFrameTime += m_editor->sprite()->frameDuration(frame);
}
m_curFrameTick = ui::clock();
m_editor->invalidate();
}
} // namespace app

View File

@ -0,0 +1,47 @@
// Aseprite
// Copyright (C) 2001-2015 David Capello
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License version 2 as
// published by the Free Software Foundation.
#ifndef APP_UI_EDITOR_PLAY_STATE_H_INCLUDED
#define APP_UI_EDITOR_PLAY_STATE_H_INCLUDED
#pragma once
#include "app/ui/editor/state_with_wheel_behavior.h"
#include "doc/frame.h"
#include "ui/timer.h"
namespace app {
class PlayState : public StateWithWheelBehavior {
public:
PlayState();
void onAfterChangeState(Editor* editor) override;
BeforeChangeAction onBeforeChangeState(Editor* editor, EditorState* newState) override;
bool onMouseDown(Editor* editor, ui::MouseMessage* msg) override;
bool onMouseUp(Editor* editor, ui::MouseMessage* msg) override;
bool onMouseMove(Editor* editor, ui::MouseMessage* msg) override;
bool onKeyDown(Editor* editor, ui::KeyMessage* msg) override;
bool onKeyUp(Editor* editor, ui::KeyMessage* msg) override;
private:
void onPlaybackTick();
Editor* m_editor;
ui::Timer m_playTimer;
// Number of milliseconds to go to the next frame if m_playTimer
// is activated.
int m_nextFrameTime;
int m_curFrameTick;
bool m_pingPongForward;
doc::frame_t m_refFrame;
};
} // namespace app
#endif

View File

@ -13,7 +13,6 @@
#include "app/app.h"
#include "app/document.h"
#include "app/handle_anidir.h"
#include "app/ini_file.h"
#include "app/loop_tag.h"
#include "app/modules/editors.h"
@ -101,7 +100,7 @@ public:
setDecorative(true);
}
bool isPlaying() { return m_isPlaying; }
bool isPlaying() const { return m_isPlaying; }
protected:
void onClick(Event& ev) override
@ -158,8 +157,6 @@ PreviewEditorWindow::PreviewEditorWindow()
, m_docView(NULL)
, m_centerButton(new MiniCenterButton())
, m_playButton(new MiniPlayButton())
, m_playTimer(10)
, m_pingPongForward(true)
, m_refFrame(0)
{
child_spacing = 0;
@ -173,8 +170,6 @@ PreviewEditorWindow::PreviewEditorWindow()
addChild(m_centerButton);
addChild(m_playButton);
m_playTimer.Tick.connect(&PreviewEditorWindow::onPlaybackTick, this);
}
PreviewEditorWindow::~PreviewEditorWindow()
@ -259,24 +254,15 @@ void PreviewEditorWindow::onCenterClicked()
void PreviewEditorWindow::onPlayClicked()
{
Editor* miniEditor = (m_docView ? m_docView->getEditor(): nullptr);
if (!miniEditor || !miniEditor->document())
return;
if (m_playButton->isPlaying()) {
if (miniEditor && miniEditor->document())
m_nextFrameTime = miniEditor->sprite()->frameDuration(miniEditor->frame());
else
m_nextFrameTime = -1;
m_curFrameTick = ui::clock();
m_pingPongForward = true;
m_playTimer.start();
}
else {
m_playTimer.stop();
if (miniEditor)
miniEditor->setFrame(m_refFrame);
m_refFrame = miniEditor->frame();
miniEditor->play();
}
else
miniEditor->stop();
}
void PreviewEditorWindow::updateUsingEditor(Editor* editor)
@ -286,6 +272,9 @@ void PreviewEditorWindow::updateUsingEditor(Editor* editor)
return;
}
if (editor != current_editor)
return;
Document* document = editor->document();
Editor* miniEditor = (m_docView ? m_docView->getEditor(): NULL);
@ -304,6 +293,8 @@ void PreviewEditorWindow::updateUsingEditor(Editor* editor)
miniEditor = m_docView->getEditor();
miniEditor->setZoom(render::Zoom(1, 1));
miniEditor->setLayer(editor->layer());
miniEditor->setFrame(editor->frame());
miniEditor->setState(EditorStatePtr(new NavigateState));
layout();
center = true;
@ -312,8 +303,24 @@ void PreviewEditorWindow::updateUsingEditor(Editor* editor)
if (center)
miniEditor->centerInSpritePoint(centerPoint);
miniEditor->setLayer(editor->layer());
miniEditor->setFrame(m_refFrame = editor->frame());
if (!m_playButton->isPlaying()) {
miniEditor->stop();
miniEditor->setLayer(editor->layer());
miniEditor->setFrame(editor->frame());
}
else {
if (miniEditor->isPlaying()) {
doc::FrameTag* tag = get_animation_tag(editor->sprite(), editor->frame());
doc::FrameTag* playingTag = get_animation_tag(editor->sprite(), m_refFrame);
if (tag != playingTag)
miniEditor->stop();
}
if (!miniEditor->isPlaying())
miniEditor->setFrame(m_refFrame = editor->frame());
miniEditor->play();
}
}
void PreviewEditorWindow::uncheckCenterButton()
@ -331,41 +338,4 @@ void PreviewEditorWindow::hideWindow()
closeWindow(NULL);
}
void PreviewEditorWindow::onPlaybackTick()
{
Editor* miniEditor = (m_docView ? m_docView->getEditor(): NULL);
if (!miniEditor)
return;
doc::Document* document = miniEditor->document();
doc::Sprite* sprite = miniEditor->sprite();
if (!document || !sprite)
return;
if (m_nextFrameTime >= 0) {
m_nextFrameTime -= (ui::clock() - m_curFrameTick);
// TODO get the frame tag in updateUsingEditor()
doc::FrameTag* tag = get_shortest_tag(sprite, m_refFrame);
if (!tag)
tag = get_loop_tag(sprite);
while (m_nextFrameTime <= 0) {
doc::frame_t frame = calculate_next_frame(
sprite,
miniEditor->frame(),
tag,
m_pingPongForward);
miniEditor->setFrame(frame);
m_nextFrameTime += miniEditor->sprite()->frameDuration(frame);
}
m_curFrameTick = ui::clock();
}
invalidate();
}
} // namespace app

View File

@ -11,7 +11,6 @@
#include "app/ui/document_view.h"
#include "doc/frame.h"
#include "ui/timer.h"
#include "ui/window.h"
namespace app {
@ -37,21 +36,12 @@ namespace app {
private:
void onCenterClicked();
void onPlayClicked();
void onPlaybackTick();
void hideWindow();
bool m_isEnabled;
DocumentView* m_docView;
MiniCenterButton* m_centerButton;
MiniPlayButton* m_playButton;
ui::Timer m_playTimer;
// Number of milliseconds to go to the next frame if m_playTimer
// is activated.
int m_nextFrameTime;
int m_curFrameTick;
bool m_pingPongForward;
doc::frame_t m_refFrame;
};

View File

@ -74,7 +74,10 @@ DocumentView* UIContext::activeView() const
void UIContext::setActiveView(DocumentView* docView)
{
if (m_lastSelectedView == docView) // Do nothing case
// Do nothing cases: 1) the view is already selected, or 2) the view
// is the a preview.
if (m_lastSelectedView == docView ||
(docView && docView->isPreview()))
return;
setActiveDocument(docView ? docView->getDocument(): NULL);