mirror of
https://github.com/aseprite/aseprite.git
synced 2025-03-10 10:13:35 +00:00
Improve animation playback performance
We try to redraw only the necessary parts of the Timeline, and when we are in playback mode, make bigger jumps to avoid a continuos scroll in the Timeline.
This commit is contained in:
parent
62ee2daaba
commit
fac3574d02
@ -1,5 +1,5 @@
|
||||
// Aseprite
|
||||
// Copyright (C) 2001-2017 David Capello
|
||||
// Copyright (C) 2001-2018 David Capello
|
||||
//
|
||||
// This program is distributed under the terms of
|
||||
// the End-User License Agreement for Aseprite.
|
||||
@ -85,7 +85,7 @@ public:
|
||||
m_previewDelegate->onPreviewOtherEditor(this);
|
||||
|
||||
if (isActive())
|
||||
set_current_palette(editor->sprite()->palette(editor->frame()), true);
|
||||
set_current_palette(editor->sprite()->palette(editor->frame()), false);
|
||||
}
|
||||
|
||||
void onAfterLayerChanged(Editor* editor) override {
|
||||
|
@ -1,5 +1,5 @@
|
||||
// Aseprite
|
||||
// Copyright (C) 2001-2017 David Capello
|
||||
// Copyright (C) 2001-2018 David Capello
|
||||
//
|
||||
// This program is distributed under the terms of
|
||||
// the End-User License Agreement for Aseprite.
|
||||
@ -212,7 +212,6 @@ void PlayState::onPlaybackTick()
|
||||
|
||||
m_editor->setFrame(frame);
|
||||
m_nextFrameTime += getNextFrameTime();
|
||||
m_editor->invalidate();
|
||||
}
|
||||
|
||||
m_curFrameTick = base::current_tick();
|
||||
|
@ -314,7 +314,6 @@ void Timeline::updateUsingEditor(Editor* editor)
|
||||
if (m_range.enabled() &&
|
||||
m_rangeLocks == 0) {
|
||||
m_range.clearRange();
|
||||
invalidate();
|
||||
}
|
||||
|
||||
// We always update the editor. In this way the timeline keeps in
|
||||
@ -414,6 +413,9 @@ void Timeline::setLayer(Layer* layer)
|
||||
{
|
||||
ASSERT(m_editor != NULL);
|
||||
|
||||
invalidateLayer(m_layer);
|
||||
invalidateLayer(layer);
|
||||
|
||||
m_layer = layer;
|
||||
|
||||
// Expand all parents
|
||||
@ -425,10 +427,9 @@ void Timeline::setLayer(Layer* layer)
|
||||
group = group->parent();
|
||||
}
|
||||
regenerateRows();
|
||||
invalidate();
|
||||
}
|
||||
|
||||
invalidate();
|
||||
|
||||
if (m_editor->layer() != layer)
|
||||
m_editor->setLayer(m_layer);
|
||||
}
|
||||
@ -443,11 +444,20 @@ void Timeline::setFrame(frame_t frame, bool byUser)
|
||||
else if (frame >= m_sprite->totalFrames())
|
||||
frame = frame_t(m_sprite->totalFrames()-1);
|
||||
|
||||
invalidateFrame(m_frame);
|
||||
invalidateFrame(frame);
|
||||
|
||||
gfx::Rect onionRc = getOnionskinFramesBounds();
|
||||
|
||||
m_frame = frame;
|
||||
invalidate();
|
||||
|
||||
// Invalidate the onionskin handles area
|
||||
onionRc |= getOnionskinFramesBounds();
|
||||
if (!onionRc.isEmpty())
|
||||
invalidateRect(onionRc.offset(origin()));
|
||||
|
||||
if (m_editor->frame() != frame) {
|
||||
bool isPlaying = m_editor->isPlaying();
|
||||
const bool isPlaying = m_editor->isPlaying();
|
||||
|
||||
if (isPlaying)
|
||||
m_editor->stop();
|
||||
@ -623,7 +633,7 @@ bool Timeline::onProcessMessage(Message* msg)
|
||||
if (selectFrame) {
|
||||
m_state = STATE_SELECTING_FRAMES;
|
||||
if (clearRange)
|
||||
m_range.clearRange();
|
||||
clearAndInvalidateRange();
|
||||
m_range.startRange(m_layer, m_clk.frame, Range::kFrames);
|
||||
m_startRange = m_range;
|
||||
|
||||
@ -647,7 +657,7 @@ bool Timeline::onProcessMessage(Message* msg)
|
||||
else if (selectLayer) {
|
||||
m_state = STATE_SELECTING_LAYERS;
|
||||
if (clearRange)
|
||||
m_range.clearRange();
|
||||
clearAndInvalidateRange();
|
||||
m_range.startRange(m_rows[m_clk.layer].layer(),
|
||||
m_frame, Range::kLayers);
|
||||
m_startRange = m_range;
|
||||
@ -689,7 +699,7 @@ bool Timeline::onProcessMessage(Message* msg)
|
||||
else {
|
||||
if (selectCel) {
|
||||
m_state = STATE_SELECTING_CELS;
|
||||
m_range.clearRange();
|
||||
clearAndInvalidateRange();
|
||||
m_range.startRange(m_rows[m_clk.layer].layer(),
|
||||
m_clk.frame, Range::kCels);
|
||||
m_startRange = m_range;
|
||||
@ -775,17 +785,26 @@ bool Timeline::onProcessMessage(Message* msg)
|
||||
}
|
||||
|
||||
case STATE_MOVING_ONIONSKIN_RANGE_LEFT: {
|
||||
gfx::Rect onionRc = getOnionskinFramesBounds();
|
||||
|
||||
int newValue = m_origFrames + (m_clk.frame - hit.frame);
|
||||
docPref().onionskin.prevFrames(MAX(0, newValue));
|
||||
invalidate();
|
||||
|
||||
onionRc |= getOnionskinFramesBounds();
|
||||
invalidateRect(onionRc.offset(origin()));
|
||||
return true;
|
||||
}
|
||||
|
||||
case STATE_MOVING_ONIONSKIN_RANGE_RIGHT:
|
||||
case STATE_MOVING_ONIONSKIN_RANGE_RIGHT: {
|
||||
gfx::Rect onionRc = getOnionskinFramesBounds();
|
||||
|
||||
int newValue = m_origFrames - (m_clk.frame - hit.frame);
|
||||
docPref().onionskin.nextFrames(MAX(0, newValue));
|
||||
invalidate();
|
||||
|
||||
onionRc |= getOnionskinFramesBounds();
|
||||
invalidateRect(onionRc.offset(origin()));
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// If the mouse pressed the mouse's button in the separator,
|
||||
@ -820,10 +839,15 @@ bool Timeline::onProcessMessage(Message* msg)
|
||||
}
|
||||
|
||||
case STATE_SELECTING_FRAMES: {
|
||||
if (m_range.enabled())
|
||||
invalidateHit(Hit(PART_RANGE_OUTLINE));
|
||||
|
||||
m_range = m_startRange;
|
||||
m_range.endRange(m_layer, hit.frame);
|
||||
|
||||
setFrame(m_clk.frame = hit.frame, true);
|
||||
|
||||
invalidateHit(Hit(PART_RANGE_OUTLINE));
|
||||
break;
|
||||
}
|
||||
|
||||
@ -1195,8 +1219,7 @@ bool Timeline::onProcessMessage(Message* msg)
|
||||
|
||||
case kKeyEsc:
|
||||
if (m_state == STATE_STANDBY) {
|
||||
m_range.clearRange();
|
||||
invalidate();
|
||||
clearAndInvalidateRange();
|
||||
}
|
||||
else {
|
||||
m_state = STATE_STANDBY;
|
||||
@ -1516,6 +1539,7 @@ void Timeline::onAfterCommandExecution(CommandExecutionEvent& ev)
|
||||
if (!m_document)
|
||||
return;
|
||||
|
||||
// TODO improve this: no need to regenerate everything after each command
|
||||
regenerateRows();
|
||||
showCurrentCel();
|
||||
invalidate();
|
||||
@ -1598,7 +1622,7 @@ void Timeline::onRemoveFrame(doc::DocumentEvent& ev)
|
||||
|
||||
// Disable the selected range when we remove frames
|
||||
if (m_range.enabled())
|
||||
m_range.clearRange();
|
||||
clearAndInvalidateRange();
|
||||
|
||||
showCurrentCel();
|
||||
clearClipboardRange();
|
||||
@ -1608,8 +1632,7 @@ void Timeline::onRemoveFrame(doc::DocumentEvent& ev)
|
||||
void Timeline::onSelectionChanged(doc::DocumentEvent& ev)
|
||||
{
|
||||
if (m_rangeLocks == 0)
|
||||
m_range.clearRange();
|
||||
invalidate();
|
||||
clearAndInvalidateRange();
|
||||
}
|
||||
|
||||
void Timeline::onLayerNameChange(doc::DocumentEvent& ev)
|
||||
@ -1644,10 +1667,9 @@ void Timeline::onAfterFrameChanged(Editor* editor)
|
||||
setFrame(editor->frame(), false);
|
||||
|
||||
if (!hasCapture())
|
||||
m_range.clearRange();
|
||||
clearAndInvalidateRange();
|
||||
|
||||
showCurrentCel();
|
||||
invalidate();
|
||||
}
|
||||
|
||||
void Timeline::onAfterLayerChanged(Editor* editor)
|
||||
@ -1659,9 +1681,7 @@ void Timeline::onAfterLayerChanged(Editor* editor)
|
||||
m_range.clearRange();
|
||||
|
||||
setLayer(editor->layer());
|
||||
|
||||
showCurrentCel();
|
||||
invalidate();
|
||||
}
|
||||
|
||||
void Timeline::onDestroyEditor(Editor* editor)
|
||||
@ -2694,6 +2714,40 @@ void Timeline::invalidateHit(const Hit& hit)
|
||||
invalidateRect(getPartBounds(hit).offset(origin()));
|
||||
}
|
||||
|
||||
void Timeline::invalidateLayer(const Layer* layer)
|
||||
{
|
||||
if (layer == nullptr)
|
||||
return;
|
||||
|
||||
layer_t layerIdx = getLayerIndex(layer);
|
||||
if (layerIdx < firstLayer())
|
||||
return;
|
||||
|
||||
gfx::Rect rc = getPartBounds(Hit(PART_ROW, layerIdx));
|
||||
gfx::Rect rcCels;
|
||||
rcCels |= getPartBounds(Hit(PART_CEL, layerIdx, firstFrame()));
|
||||
rcCels |= getPartBounds(Hit(PART_CEL, layerIdx, lastFrame()));
|
||||
rcCels &= getCelsBounds();
|
||||
rc |= rcCels;
|
||||
rc.offset(origin());
|
||||
invalidateRect(rc);
|
||||
}
|
||||
|
||||
void Timeline::invalidateFrame(const frame_t frame)
|
||||
{
|
||||
if (!validFrame(frame))
|
||||
return;
|
||||
|
||||
gfx::Rect rc = getPartBounds(Hit(PART_HEADER_FRAME, -1, frame));
|
||||
gfx::Rect rcCels;
|
||||
rcCels |= getPartBounds(Hit(PART_CEL, firstLayer(), frame));
|
||||
rcCels |= getPartBounds(Hit(PART_CEL, lastLayer(), frame));
|
||||
rcCels &= getCelsBounds();
|
||||
rc |= rcCels;
|
||||
rc.offset(origin());
|
||||
invalidateRect(rc);
|
||||
}
|
||||
|
||||
void Timeline::regenerateRows()
|
||||
{
|
||||
ASSERT(m_document);
|
||||
@ -3011,9 +3065,7 @@ void Timeline::setHot(const Hit& hit)
|
||||
// If the part, layer or frame change.
|
||||
if (m_hot != hit) {
|
||||
// Invalidate the whole control.
|
||||
if (m_state == STATE_MOVING_RANGE ||
|
||||
hit.part == PART_RANGE_OUTLINE ||
|
||||
m_hot.part == PART_RANGE_OUTLINE) {
|
||||
if (isMovingCel()) {
|
||||
invalidate();
|
||||
}
|
||||
// Invalidate the old and new 'hot' thing.
|
||||
@ -3199,14 +3251,20 @@ void Timeline::showCel(layer_t layer, frame_t frame)
|
||||
viewport.y + layerBoxHeight()*(lastLayer() - layer) - scroll.y,
|
||||
frameBoxWidth(), layerBoxHeight());
|
||||
|
||||
const bool isPlaying = m_editor->isPlaying();
|
||||
|
||||
// Here we use <= instead of < to avoid jumping between this
|
||||
// condition and the "else if" one when we are playing the
|
||||
// animation.
|
||||
if (celBounds.x <= viewport.x) {
|
||||
scroll.x -= viewport.x - celBounds.x;
|
||||
if (isPlaying)
|
||||
scroll.x -= viewport.w/2 - celBounds.w/2;
|
||||
}
|
||||
else if (celBounds.x2() > viewport.x2()) {
|
||||
scroll.x += celBounds.x2() - viewport.x2();
|
||||
if (isPlaying)
|
||||
scroll.x += viewport.w/2 - celBounds.w/2;
|
||||
}
|
||||
|
||||
if (celBounds.y <= viewport.y) {
|
||||
@ -3419,12 +3477,26 @@ void Timeline::setViewScroll(const gfx::Point& pt)
|
||||
newScroll.x = MID(0, newScroll.x, maxPos.x);
|
||||
newScroll.y = MID(0, newScroll.y, maxPos.y);
|
||||
|
||||
if (newScroll == oldScroll)
|
||||
return;
|
||||
if (newScroll.y != oldScroll.y) {
|
||||
gfx::Rect rc;
|
||||
rc |= getPartBounds(Hit(PART_ROW, 0));
|
||||
rc |= getPartBounds(Hit(PART_ROW, lastLayer()));
|
||||
rc.offset(origin());
|
||||
invalidateRect(rc);
|
||||
}
|
||||
|
||||
if (newScroll != oldScroll) {
|
||||
gfx::Rect rc;
|
||||
if (m_tagBands > 0)
|
||||
rc |= getPartBounds(Hit(PART_FRAME_TAG_BAND));
|
||||
rc |= getFrameHeadersBounds();
|
||||
rc |= getCelsBounds();
|
||||
rc.offset(origin());
|
||||
invalidateRect(rc);
|
||||
}
|
||||
|
||||
m_hbar.setPos(newScroll.x);
|
||||
m_vbar.setPos(newScroll.y);
|
||||
invalidate();
|
||||
}
|
||||
|
||||
|
||||
@ -3510,6 +3582,19 @@ void Timeline::clearClipboardRange()
|
||||
m_clipboard_timer.stop();
|
||||
}
|
||||
|
||||
void Timeline::clearAndInvalidateRange()
|
||||
{
|
||||
if (m_range.enabled()) {
|
||||
for (const Layer* layer : m_range.selectedLayers())
|
||||
invalidateLayer(layer);
|
||||
for (const frame_t frame : m_range.selectedFrames())
|
||||
invalidateFrame(frame);
|
||||
|
||||
invalidateHit(Hit(PART_RANGE_OUTLINE));
|
||||
m_range.clearRange();
|
||||
}
|
||||
}
|
||||
|
||||
DocumentPreferences& Timeline::docPref() const
|
||||
{
|
||||
return Preferences::instance().document(m_document);
|
||||
@ -3686,7 +3771,7 @@ bool Timeline::onClear(Context* ctx)
|
||||
void Timeline::onCancel(Context* ctx)
|
||||
{
|
||||
if (m_rangeLocks == 0)
|
||||
m_range.clearRange();
|
||||
clearAndInvalidateRange();
|
||||
|
||||
clearClipboardRange();
|
||||
invalidate();
|
||||
|
@ -262,6 +262,8 @@ namespace app {
|
||||
gfx::Rect getPartBounds(const Hit& hit) const;
|
||||
gfx::Rect getRangeBounds(const Range& range) const;
|
||||
void invalidateHit(const Hit& hit);
|
||||
void invalidateLayer(const Layer* layer);
|
||||
void invalidateFrame(const frame_t frame);
|
||||
void regenerateRows();
|
||||
void regenerateTagBands();
|
||||
int visibleTagBands() const;
|
||||
@ -282,6 +284,7 @@ namespace app {
|
||||
void updateStatusBar(ui::Message* msg);
|
||||
void updateDropRange(const gfx::Point& pt);
|
||||
void clearClipboardRange();
|
||||
void clearAndInvalidateRange();
|
||||
|
||||
// The layer of the bottom (e.g. Background layer)
|
||||
layer_t firstLayer() const { return 0; }
|
||||
|
Loading…
x
Reference in New Issue
Block a user