Fix Timeline operations with ranges using virtual vs real ranges (#3904)

- Now we have a view::RealRange (with real frames, fr_t) and a
  view::VirtualRange (with virtual frames, columns, col_t). The timeline
  uses a virtual range internally and makes the conversion to a real
  range when it's appropiated (e.g. to execute an old DocRange operation
  using ranges with real frames).

- Added Context::range() to access to the real selected range
  instead of accessing directly to the timeline. In this way commands
  that were using the DocRange can access to the real range using the
  context, instead of the timeline's virtual range.

- Added a new ShowTagTimelineAdapter that can show just one tag in the
  timeline filtering out/hiding all other frames/tags.
This commit is contained in:
David Capello 2023-06-08 19:00:49 -03:00
parent 8a2df3b01d
commit be17c48324
33 changed files with 381 additions and 190 deletions

View File

@ -1,4 +1,5 @@
// Aseprite // Aseprite
// Copyright (C) 2023 Igara Studio SA
// Copyright (C) 2001-2018 David Capello // Copyright (C) 2001-2018 David Capello
// //
// This program is distributed under the terms of // This program is distributed under the terms of
@ -43,6 +44,8 @@ namespace app {
virtual size_t onMemSize() const; virtual size_t onMemSize() const;
private: private:
// TODO I think we could just remove this field (but we'll need to
// include the Context* in all onEvent() member functions)
Context* m_ctx; Context* m_ctx;
#if _DEBUG #if _DEBUG
enum class State { NotExecuted, Executed, Undone, Redone }; enum class State { NotExecuted, Executed, Undone, Redone };

View File

@ -1,5 +1,5 @@
// Aseprite // Aseprite
// Copyright (C) 2019-2022 Igara Studio S.A. // Copyright (C) 2019-2023 Igara Studio S.A.
// Copyright (C) 2001-2018 David Capello // Copyright (C) 2001-2018 David Capello
// //
// This program is distributed under the terms of // This program is distributed under the terms of
@ -14,8 +14,6 @@
#include "app/app.h" #include "app/app.h"
#include "app/context.h" #include "app/context.h"
#include "app/site.h" #include "app/site.h"
#include "app/sprite_position.h"
#include "app/ui/timeline/timeline.h"
namespace app { namespace app {
@ -41,7 +39,7 @@ CmdTransaction* CmdTransaction::moveToEmptyCopy()
return copy; return copy;
} }
void CmdTransaction::setNewDocRange(const DocRange& range) void CmdTransaction::setNewDocRange(const view::RealRange& range)
{ {
if (m_ranges) if (m_ranges)
range.write(m_ranges->m_after); range.write(m_ranges->m_after);
@ -131,24 +129,12 @@ SpritePosition CmdTransaction::calcSpritePosition() const
bool CmdTransaction::isDocRangeEnabled() const bool CmdTransaction::isDocRangeEnabled() const
{ {
if (App::instance()) { return context()->range().enabled();
Timeline* timeline = App::instance()->timeline();
if (timeline && timeline->range().enabled())
return true;
}
return false;
} }
DocRange CmdTransaction::calcDocRange() const view::RealRange CmdTransaction::calcDocRange() const
{ {
// TODO We cannot use Context::activeSite() because it losts return context()->range();
// important information about the DocRange() (type and
// flags).
if (App* app = App::instance()) {
if (Timeline* timeline = app->timeline())
return timeline->range();
}
return DocRange();
} }
} // namespace app } // namespace app

View File

@ -1,5 +1,5 @@
// Aseprite // Aseprite
// Copyright (C) 2019-2022 Igara Studio S.A. // Copyright (C) 2019-2023 Igara Studio S.A.
// Copyright (C) 2001-2018 David Capello // Copyright (C) 2001-2018 David Capello
// //
// This program is distributed under the terms of // This program is distributed under the terms of
@ -10,8 +10,8 @@
#pragma once #pragma once
#include "app/cmd_sequence.h" #include "app/cmd_sequence.h"
#include "app/doc_range.h"
#include "app/sprite_position.h" #include "app/sprite_position.h"
#include "view/range.h"
#include <memory> #include <memory>
#include <sstream> #include <sstream>
@ -32,7 +32,7 @@ namespace app {
// the new CmdTransaction. // the new CmdTransaction.
CmdTransaction* moveToEmptyCopy(); CmdTransaction* moveToEmptyCopy();
void setNewDocRange(const DocRange& range); void setNewDocRange(const view::RealRange& range);
void updateSpritePositionAfter(); void updateSpritePositionAfter();
SpritePosition spritePositionBeforeExecute() const { return m_spritePositionBefore; } SpritePosition spritePositionBeforeExecute() const { return m_spritePositionBefore; }
@ -51,7 +51,7 @@ namespace app {
private: private:
SpritePosition calcSpritePosition() const; SpritePosition calcSpritePosition() const;
bool isDocRangeEnabled() const; bool isDocRangeEnabled() const;
DocRange calcDocRange() const; view::RealRange calcDocRange() const;
struct Ranges { struct Ranges {
std::stringstream m_before; std::stringstream m_before;

View File

@ -95,7 +95,7 @@ public:
m_timer.stop(); m_timer.stop();
m_document = doc; m_document = doc;
m_cel = cel; m_cel = cel;
m_range = App::instance()->timeline()->range(); m_range = UIContext::instance()->range();
if (m_document) if (m_document)
m_document->add_observer(this); m_document->add_observer(this);
@ -276,7 +276,7 @@ private:
m_lastValues.text = newUserData.text(); m_lastValues.text = newUserData.text();
if (redrawTimeline) if (redrawTimeline)
App::instance()->timeline()->invalidate(); App::instance()->timeline()->invalidate(); // TODO avoid this invalidating in tx.commit()
tx.commit(); tx.commit();
} }
@ -378,7 +378,7 @@ private:
bool m_pendingChanges = false; bool m_pendingChanges = false;
Doc* m_document = nullptr; Doc* m_document = nullptr;
Cel* m_cel = nullptr; Cel* m_cel = nullptr;
DocRange m_range; view::RealRange m_range;
bool m_selfUpdate = false; bool m_selfUpdate = false;
UserDataView m_userDataView; UserDataView m_userDataView;
CelPropsLastValues m_lastValues; CelPropsLastValues m_lastValues;

View File

@ -1,4 +1,5 @@
// Aseprite // Aseprite
// Copyright (C) 2023 Igara Studio S.A.
// Copyright (C) 2017 David Capello // Copyright (C) 2017 David Capello
// //
// This program is distributed under the terms of // This program is distributed under the terms of
@ -12,7 +13,6 @@
#include "app/commands/command.h" #include "app/commands/command.h"
#include "app/context_access.h" #include "app/context_access.h"
#include "app/modules/gui.h" #include "app/modules/gui.h"
#include "app/ui/timeline/timeline.h"
#include "doc/image.h" #include "doc/image.h"
#include "doc/layer.h" #include "doc/layer.h"
@ -49,7 +49,7 @@ bool LayerLockCommand::onChecked(Context* context)
return false; return false;
SelectedLayers selLayers; SelectedLayers selLayers;
auto range = App::instance()->timeline()->range(); const view::RealRange& range = context->range();
if (range.enabled()) { if (range.enabled()) {
selLayers = range.selectedLayers(); selLayers = range.selectedLayers();
} }
@ -68,7 +68,7 @@ void LayerLockCommand::onExecute(Context* context)
{ {
ContextWriter writer(context); ContextWriter writer(context);
SelectedLayers selLayers; SelectedLayers selLayers;
auto range = App::instance()->timeline()->range(); auto range = context->range();
if (range.enabled()) { if (range.enabled()) {
selLayers = range.selectedLayers(); selLayers = range.selectedLayers();
} }

View File

@ -157,7 +157,7 @@ public:
m_timer.stop(); m_timer.stop();
m_document = doc; m_document = doc;
m_layer = layer; m_layer = layer;
m_range = App::instance()->timeline()->range(); m_range = UIContext::instance()->range();
if (m_document) if (m_document)
m_document->add_observer(this); m_document->add_observer(this);
@ -267,11 +267,11 @@ private:
ContextWriter writer(UIContext::instance()); ContextWriter writer(UIContext::instance());
Tx tx(writer, "Set Layer Properties"); Tx tx(writer, "Set Layer Properties");
DocRange range; view::RealRange range;
if (m_range.enabled()) if (m_range.enabled())
range = m_range; range = m_range;
else { else {
range.startRange(m_layer, -1, DocRange::kLayers); range.startRange(m_layer, -1, view::Range::kLayers);
range.endRange(m_layer, -1); range.endRange(m_layer, -1);
} }
@ -474,7 +474,7 @@ private:
bool m_pendingChanges = false; bool m_pendingChanges = false;
Doc* m_document = nullptr; Doc* m_document = nullptr;
Layer* m_layer = nullptr; Layer* m_layer = nullptr;
DocRange m_range; view::RealRange m_range;
bool m_selfUpdate = false; bool m_selfUpdate = false;
UserDataView m_userDataView; UserDataView m_userDataView;
}; };

View File

@ -1,5 +1,5 @@
// Aseprite // Aseprite
// Copyright (C) 2024 Igara Studio S.A. // Copyright (C) 2023-2024 Igara Studio S.A.
// Copyright (C) 2001-2017 David Capello // Copyright (C) 2001-2017 David Capello
// //
// This program is distributed under the terms of // This program is distributed under the terms of
@ -12,7 +12,6 @@
#include "app/commands/command.h" #include "app/commands/command.h"
#include "app/context_access.h" #include "app/context_access.h"
#include "app/modules/gui.h" #include "app/modules/gui.h"
#include "app/ui/timeline/timeline.h"
#include "doc/image.h" #include "doc/image.h"
#include "doc/layer.h" #include "doc/layer.h"
@ -49,7 +48,7 @@ bool LayerVisibilityCommand::onChecked(Context* context)
return false; return false;
SelectedLayers selLayers; SelectedLayers selLayers;
DocRange range = context->activeSite().range(); auto range = context->range();
if (range.enabled()) { if (range.enabled()) {
selLayers = range.selectedLayers(); selLayers = range.selectedLayers();
} }
@ -69,7 +68,7 @@ void LayerVisibilityCommand::onExecute(Context* context)
ContextWriter writer(context); ContextWriter writer(context);
Doc* doc = writer.document(); Doc* doc = writer.document();
SelectedLayers selLayers; SelectedLayers selLayers;
DocRange range = context->activeSite().range(); auto range = context->range();
if (range.enabled()) { if (range.enabled()) {
selLayers = range.selectedLayers(); selLayers = range.selectedLayers();
} }

View File

@ -129,7 +129,7 @@ void NewFrameCommand::onExecute(Context* context)
if (timeline) if (timeline)
timeline->prepareToMoveRange(); timeline->prepareToMoveRange();
DocRange range = site.range(); view::RealRange range = site.range();
SelectedLayers selLayers; SelectedLayers selLayers;
if (site.inFrames()) if (site.inFrames())

View File

@ -1,5 +1,5 @@
// Aseprite // Aseprite
// Copyright (C) 2019-2022 Igara Studio S.A. // Copyright (C) 2019-2023 Igara Studio S.A.
// Copyright (C) 2001-2018 David Capello // Copyright (C) 2001-2018 David Capello
// //
// This program is distributed under the terms of // This program is distributed under the terms of
@ -16,7 +16,6 @@
#include "app/context_access.h" #include "app/context_access.h"
#include "app/tx.h" #include "app/tx.h"
#include "app/ui/tag_window.h" #include "app/ui/tag_window.h"
#include "app/ui/timeline/timeline.h"
#include "doc/tag.h" #include "doc/tag.h"
#include <stdexcept> #include <stdexcept>
@ -52,10 +51,10 @@ void NewFrameTagCommand::onExecute(Context* context)
frame_t from = reader.frame(); frame_t from = reader.frame();
frame_t to = reader.frame(); frame_t to = reader.frame();
auto range = App::instance()->timeline()->range(); view::RealRange range = context->range();
if (range.enabled() && if (range.enabled() &&
(range.type() == DocRange::kFrames || (range.type() == view::Range::kFrames ||
range.type() == DocRange::kCels)) { range.type() == view::Range::kCels)) {
from = range.selectedFrames().firstFrame(); from = range.selectedFrames().firstFrame();
to = range.selectedFrames().lastFrame(); to = range.selectedFrames().lastFrame();
} }
@ -79,8 +78,6 @@ void NewFrameTagCommand::onExecute(Context* context)
tag.release(); tag.release();
tx.commit(); tx.commit();
} }
App::instance()->timeline()->invalidate();
} }
Command* CommandFactory::createNewFrameTagCommand() Command* CommandFactory::createNewFrameTagCommand()

View File

@ -1,4 +1,5 @@
// Aseprite // Aseprite
// Copyright (C) 2023 Igara Studio SA
// Copyright (C) 2001-2018 David Capello // Copyright (C) 2001-2018 David Capello
// //
// This program is distributed under the terms of // This program is distributed under the terms of
@ -33,7 +34,7 @@ ReverseFramesCommand::ReverseFramesCommand()
bool ReverseFramesCommand::onEnabled(Context* context) bool ReverseFramesCommand::onEnabled(Context* context)
{ {
auto range = App::instance()->timeline()->range(); const view::RealRange& range = context->range();
return return
context->checkFlags(ContextFlags::ActiveDocumentIsWritable) && context->checkFlags(ContextFlags::ActiveDocumentIsWritable) &&
range.enabled() && range.enabled() &&
@ -42,7 +43,7 @@ bool ReverseFramesCommand::onEnabled(Context* context)
void ReverseFramesCommand::onExecute(Context* context) void ReverseFramesCommand::onExecute(Context* context)
{ {
auto range = App::instance()->timeline()->range(); const view::RealRange& range = context->range();
if (!range.enabled()) if (!range.enabled())
return; // Nothing to do return; // Nothing to do

View File

@ -113,12 +113,12 @@ void SelectPaletteColorsCommand::onExecute(Context* context)
{ {
Site site = context->activeSite(); Site site = context->activeSite();
Sprite* sprite = site.sprite(); Sprite* sprite = site.sprite();
DocRange range = site.range(); view::RealRange range = site.range();
SelectedFrames selectedFrames; SelectedFrames selectedFrames;
SelectedLayers selectedLayers; SelectedLayers selectedLayers;
if (range.type() == DocRange::Type::kNone) { if (range.type() == view::Range::Type::kNone) {
// If there isn't a cels range selected, it assumes the whole sprite: // If there isn't a cels range selected, it assumes the whole sprite:
range.startRange(site.layer(), 0, DocRange::Type::kFrames); range.startRange(site.layer(), 0, view::Range::Type::kFrames);
range.endRange(site.layer(), sprite->lastFrame()); range.endRange(site.layer(), sprite->lastFrame());
selectedFrames = range.selectedFrames(); selectedFrames = range.selectedFrames();
selectedLayers.selectAllLayers(sprite->root()); selectedLayers.selectAllLayers(sprite->root());

View File

@ -80,7 +80,7 @@ void SetLoopSectionCommand::onExecute(Context* ctx)
switch (m_action) { switch (m_action) {
case Action::Auto: { case Action::Auto: {
auto range = App::instance()->timeline()->range(); const view::RealRange& range = ctx->range();
if (range.enabled() && (range.frames() > 1)) { if (range.enabled() && (range.frames() > 1)) {
begin = range.selectedFrames().firstFrame(); begin = range.selectedFrames().firstFrame();
end = range.selectedFrames().lastFrame(); end = range.selectedFrames().lastFrame();

View File

@ -144,12 +144,9 @@ void UndoCommand::onExecute(Context* context)
// this point when objects (possible layers) are re-created after // this point when objects (possible layers) are re-created after
// the undo and we can deserialize them. // the undo and we can deserialize them.
if (docRangeStream) { if (docRangeStream) {
Timeline* timeline = App::instance()->timeline(); view::Range docRange;
if (timeline) { if (docRange.read(*docRangeStream))
DocRange docRange; context->setRange(docRange);
if (docRange.read(*docRangeStream))
timeline->setRange(docRange);
}
} }
document->generateMaskBoundaries(); document->generateMaskBoundaries();

View File

@ -91,6 +91,14 @@ Doc* Context::activeDocument() const
return site.document(); return site.document();
} }
const view::RealRange& Context::range() const
{
Site site;
onGetActiveSite(&site);
m_range = site.range(); // TODO cache this value as much as possible
return m_range;
}
void Context::setActiveDocument(Doc* document) void Context::setActiveDocument(Doc* document)
{ {
onSetActiveDocument(document, true); onSetActiveDocument(document, true);
@ -106,7 +114,7 @@ void Context::setActiveFrame(const doc::frame_t frame)
onSetActiveFrame(frame); onSetActiveFrame(frame);
} }
void Context::setRange(const DocRange& range) void Context::setRange(const view::RealRange& range)
{ {
onSetRange(range); onSetRange(range);
} }
@ -299,7 +307,7 @@ void Context::onSetActiveFrame(const doc::frame_t frame)
notifyActiveSiteChanged(); notifyActiveSiteChanged();
} }
void Context::onSetRange(const DocRange& range) void Context::onSetRange(const view::RealRange& range)
{ {
if (m_lastSelectedDoc) if (m_lastSelectedDoc)
activeSiteHandler()->setRangeInDoc(m_lastSelectedDoc, range); activeSiteHandler()->setRangeInDoc(m_lastSelectedDoc, range);

View File

@ -22,6 +22,7 @@
#include "obs/observable.h" #include "obs/observable.h"
#include "obs/signal.h" #include "obs/signal.h"
#include "os/surface.h" #include "os/surface.h"
#include "view/range.h"
#include <memory> #include <memory>
#include <vector> #include <vector>
@ -31,10 +32,6 @@ namespace doc {
class PalettePicks; class PalettePicks;
} }
namespace view {
class Range;
}
namespace app { namespace app {
class ActiveSiteHandler; class ActiveSiteHandler;
class Clipboard; class Clipboard;
@ -134,10 +131,11 @@ namespace app {
Site activeSite() const; Site activeSite() const;
Doc* activeDocument() const; Doc* activeDocument() const;
const view::RealRange& range() const;
void setActiveDocument(Doc* document); void setActiveDocument(Doc* document);
void setActiveLayer(doc::Layer* layer); void setActiveLayer(doc::Layer* layer);
void setActiveFrame(doc::frame_t frame); void setActiveFrame(doc::frame_t frame);
void setRange(const view::Range& range); void setRange(const view::RealRange& range);
void setSelectedColors(const doc::PalettePicks& picks); void setSelectedColors(const doc::PalettePicks& picks);
void setSelectedTiles(const doc::PalettePicks& picks); void setSelectedTiles(const doc::PalettePicks& picks);
bool hasModifiedDocuments() const; bool hasModifiedDocuments() const;
@ -170,7 +168,7 @@ namespace app {
virtual void onSetActiveDocument(Doc* doc, bool notify); virtual void onSetActiveDocument(Doc* doc, bool notify);
virtual void onSetActiveLayer(doc::Layer* layer); virtual void onSetActiveLayer(doc::Layer* layer);
virtual void onSetActiveFrame(const doc::frame_t frame); virtual void onSetActiveFrame(const doc::frame_t frame);
virtual void onSetRange(const view::Range& range); virtual void onSetRange(const view::RealRange& range);
virtual void onSetSelectedColors(const doc::PalettePicks& picks); virtual void onSetSelectedColors(const doc::PalettePicks& picks);
virtual void onSetSelectedTiles(const doc::PalettePicks& picks); virtual void onSetSelectedTiles(const doc::PalettePicks& picks);
virtual void onCloseDocument(Doc* doc); virtual void onCloseDocument(Doc* doc);
@ -188,6 +186,7 @@ namespace app {
Doc* m_lastSelectedDoc; Doc* m_lastSelectedDoc;
mutable std::unique_ptr<Preferences> m_preferences; mutable std::unique_ptr<Preferences> m_preferences;
std::unique_ptr<DraggedData> m_draggedData = nullptr; std::unique_ptr<DraggedData> m_draggedData = nullptr;
mutable view::RealRange m_range; // Last/current range
// Result of the execution of a command. // Result of the execution of a command.
CommandResult m_result; CommandResult m_result;

View File

@ -1,5 +1,5 @@
// Aseprite // Aseprite
// Copyright (C) 2019-2022 Igara Studio S.A. // Copyright (C) 2019-2023 Igara Studio S.A.
// Copyright (C) 2001-2018 David Capello // Copyright (C) 2001-2018 David Capello
// //
// This program is distributed under the terms of // This program is distributed under the terms of
@ -65,13 +65,13 @@ Palette* Site::palette() const
return (m_sprite ? m_sprite->palette(m_frame): nullptr); return (m_sprite ? m_sprite->palette(m_frame): nullptr);
} }
void Site::range(const DocRange& range) void Site::range(const view::RealRange& range)
{ {
m_range = range; m_range = range;
switch (range.type()) { switch (range.type()) {
case DocRange::kCels: m_focus = Site::InCels; break; case view::Range::kCels: m_focus = Site::InCels; break;
case DocRange::kFrames: m_focus = Site::InFrames; break; case view::Range::kFrames: m_focus = Site::InFrames; break;
case DocRange::kLayers: m_focus = Site::InLayers; break; case view::Range::kLayers: m_focus = Site::InLayers; break;
} }
} }

View File

@ -9,7 +9,6 @@
#define APP_SITE_H_INCLUDED #define APP_SITE_H_INCLUDED
#pragma once #pragma once
#include "app/doc_range.h"
#include "app/tilemap_mode.h" #include "app/tilemap_mode.h"
#include "app/tileset_mode.h" #include "app/tileset_mode.h"
#include "doc/cel_list.h" #include "doc/cel_list.h"
@ -17,6 +16,7 @@
#include "doc/palette_picks.h" #include "doc/palette_picks.h"
#include "doc/selected_objects.h" #include "doc/selected_objects.h"
#include "gfx/fwd.h" #include "gfx/fwd.h"
#include "view/range.h"
namespace doc { namespace doc {
class Grid; class Grid;
@ -70,14 +70,14 @@ namespace app {
doc::Layer* layer() const { return m_layer; } doc::Layer* layer() const { return m_layer; }
doc::frame_t frame() const { return m_frame; } doc::frame_t frame() const { return m_frame; }
doc::Cel* cel() const; doc::Cel* cel() const;
const DocRange& range() const { return m_range; } const view::RealRange& range() const { return m_range; }
void focus(Focus focus) { m_focus = focus; } void focus(Focus focus) { m_focus = focus; }
void document(Doc* document) { m_document = document; } void document(Doc* document) { m_document = document; }
void sprite(doc::Sprite* sprite) { m_sprite = sprite; } void sprite(doc::Sprite* sprite) { m_sprite = sprite; }
void layer(doc::Layer* layer) { m_layer = layer; } void layer(doc::Layer* layer) { m_layer = layer; }
void frame(doc::frame_t frame) { m_frame = frame; } void frame(doc::frame_t frame) { m_frame = frame; }
void range(const DocRange& range); void range(const view::RealRange& range);
const doc::SelectedLayers& selectedLayers() const { return m_range.selectedLayers(); } const doc::SelectedLayers& selectedLayers() const { return m_range.selectedLayers(); }
const doc::SelectedFrames& selectedFrames() const { return m_range.selectedFrames(); } const doc::SelectedFrames& selectedFrames() const { return m_range.selectedFrames(); }
@ -132,7 +132,7 @@ namespace app {
doc::Sprite* m_sprite; doc::Sprite* m_sprite;
doc::Layer* m_layer; doc::Layer* m_layer;
doc::frame_t m_frame; doc::frame_t m_frame;
DocRange m_range; view::RealRange m_range;
doc::PalettePicks m_selectedColors; doc::PalettePicks m_selectedColors;
doc::PalettePicks m_selectedTiles; doc::PalettePicks m_selectedTiles;
doc::SelectedObjects m_selectedSlices; doc::SelectedObjects m_selectedSlices;

View File

@ -19,6 +19,10 @@
#include <stdexcept> #include <stdexcept>
namespace view {
class Range;
}
namespace app { namespace app {
// Wrapper to create a new transaction or get the current // Wrapper to create a new transaction or get the current
@ -105,7 +109,7 @@ namespace app {
m_transaction->commit(); m_transaction->commit();
} }
void setNewDocRange(const DocRange& range) { void setNewDocRange(const view::Range& range) {
m_transaction->setNewDocRange(range); m_transaction->setNewDocRange(range);
} }

View File

@ -441,8 +441,8 @@ void Editor::getSite(Site* site) const
Timeline* timeline = App::instance()->timeline(); Timeline* timeline = App::instance()->timeline();
if (timeline && if (timeline &&
timeline->isVisible() && timeline->isVisible() &&
timeline->range().enabled()) { timeline->isRangeEnabled()) {
site->range(timeline->range()); site->range(timeline->realRange());
} }
if (m_layer && m_layer->isTilemap()) { if (m_layer && m_layer->isTilemap()) {

View File

@ -49,7 +49,7 @@ MovingCelCollect::MovingCelCollect(Editor* editor, Layer* layer)
m_mainCel = layer->cel(editor->frame()); m_mainCel = layer->cel(editor->frame());
Timeline* timeline = App::instance()->timeline(); Timeline* timeline = App::instance()->timeline();
DocRange range = timeline->range(); view::RealRange range = timeline->realRange();
if (!range.enabled() || if (!range.enabled() ||
!timeline->isVisible()) { !timeline->isVisible()) {
range.startRange(editor->layer(), editor->frame(), DocRange::kCels); range.startRange(editor->layer(), editor->frame(), DocRange::kCels);

View File

@ -66,7 +66,7 @@ MovingSliceState::MovingSliceState(Editor* editor,
} }
if (editor->slicesTransforms() && !m_items.empty()) { if (editor->slicesTransforms() && !m_items.empty()) {
DocRange range = m_site.range(); view::Range range = m_site.range();
SelectedLayers selectedLayers = range.selectedLayers(); SelectedLayers selectedLayers = range.selectedLayers();
// Do not take into account invisible layers. // Do not take into account invisible layers.
for (auto it = selectedLayers.begin(); it != selectedLayers.end(); ++it) { for (auto it = selectedLayers.begin(); it != selectedLayers.end(); ++it) {

View File

@ -187,7 +187,7 @@ bool PixelsMovement::editMultipleCels() const
return return
(m_site.range().enabled() && (m_site.range().enabled() &&
(Preferences::instance().selection.multicelWhenLayersOrFrames() || (Preferences::instance().selection.multicelWhenLayersOrFrames() ||
m_site.range().type() == DocRange::kCels)); m_site.range().type() == view::Range::kCels));
} }
void PixelsMovement::setDelegate(PixelsMovementDelegate* delegate) void PixelsMovement::setDelegate(PixelsMovementDelegate* delegate)

View File

@ -49,7 +49,6 @@
#include "app/ui/main_window.h" #include "app/ui/main_window.h"
#include "app/ui/skin/skin_theme.h" #include "app/ui/skin/skin_theme.h"
#include "app/ui/status_bar.h" #include "app/ui/status_bar.h"
#include "app/ui/timeline/timeline.h"
#include "app/ui_context.h" #include "app/ui_context.h"
#include "app/util/layer_utils.h" #include "app/util/layer_utils.h"
#include "app/util/new_image_from_mask.h" #include "app/util/new_image_from_mask.h"
@ -165,7 +164,7 @@ bool StandbyState::onMouseDown(Editor* editor, MouseMessage* msg)
editor->projection(), editor->projection(),
ColorPicker::FromComposition); ColorPicker::FromComposition);
auto range = App::instance()->timeline()->range(); const view::RealRange& range = context->range();
if (picker.layer() && if (picker.layer() &&
!range.contains(picker.layer())) { !range.contains(picker.layer())) {
layer = picker.layer(); layer = picker.layer();

View File

@ -1077,7 +1077,8 @@ void KeyboardShortcuts::disableAccel(const ui::Accelerator& accel,
KeyContext KeyboardShortcuts::getCurrentKeyContext() const KeyContext KeyboardShortcuts::getCurrentKeyContext() const
{ {
Doc* doc = UIContext::instance()->activeDocument(); auto ctx = UIContext::instance();
Doc* doc = ctx->activeDocument();
if (doc && if (doc &&
doc->isMaskVisible() && doc->isMaskVisible() &&
// The active key context will be the selectedTool() (in the // The active key context will be the selectedTool() (in the
@ -1095,11 +1096,11 @@ KeyContext KeyboardShortcuts::getCurrentKeyContext() const
return KeyContext::SelectionTool; return KeyContext::SelectionTool;
} }
auto timeline = App::instance()->timeline(); const view::RealRange& range = ctx->range();
if (doc && timeline && if (doc &&
!timeline->selectedFrames().empty() && !range.selectedFrames().empty() &&
(timeline->range().type() == DocRange::kFrames || (range.type() == view::Range::kFrames ||
timeline->range().type() == DocRange::kCels)) { range.type() == view::Range::kCels)) {
return KeyContext::FramesSelection; return KeyContext::FramesSelection;
} }

View File

@ -63,6 +63,7 @@
#include "ui/ui.h" #include "ui/ui.h"
#include "view/layers.h" #include "view/layers.h"
#include "view/timeline_adapter.h" #include "view/timeline_adapter.h"
#include "view/utils.h"
#include <algorithm> #include <algorithm>
#include <cstdio> #include <cstdio>
@ -383,7 +384,9 @@ void Timeline::updateUsingEditor(Editor* editor)
m_document = site.document(); m_document = site.document();
m_sprite = site.sprite(); m_sprite = site.sprite();
m_layer = site.layer(); m_layer = site.layer();
m_adapter = std::make_unique<view::FullSpriteTimelineAdapter>(m_sprite);
updateTimelineAdapter(false);
m_frame = m_adapter->toColFrame(fr_t(site.frame())); m_frame = m_adapter->toColFrame(fr_t(site.frame()));
m_state = STATE_STANDBY; m_state = STATE_STANDBY;
m_hot.part = PART_NOTHING; m_hot.part = PART_NOTHING;
@ -532,6 +535,11 @@ void Timeline::setFrame(col_t frame, bool byUser)
} }
} }
view::RealRange Timeline::realRange() const
{
return view::to_real_range(m_adapter.get(), m_range);
}
void Timeline::prepareToMoveRange() void Timeline::prepareToMoveRange()
{ {
ASSERT(m_range.enabled()); ASSERT(m_range.enabled());
@ -554,7 +562,7 @@ void Timeline::prepareToMoveRange()
m_moveRangeData.activeRelativeFrame = j; m_moveRangeData.activeRelativeFrame = j;
} }
void Timeline::moveRange(const Range& range) void Timeline::moveRange(const VirtualRange& range)
{ {
regenerateCols(); regenerateCols();
regenerateRows(); regenerateRows();
@ -587,12 +595,18 @@ void Timeline::moveRange(const Range& range)
m_range = range; m_range = range;
} }
void Timeline::setRange(const Range& range) void Timeline::setVirtualRange(const VirtualRange& range)
{ {
m_range = range; m_range = range;
invalidate(); invalidate();
} }
void Timeline::setRealRange(const RealRange& range)
{
m_range = view::to_virtual_range(m_adapter.get(), range);
invalidate();
}
void Timeline::activateClipboardRange() void Timeline::activateClipboardRange()
{ {
m_clipboard_timer.start(); m_clipboard_timer.start();
@ -964,7 +978,7 @@ bool Timeline::onProcessMessage(Message* msg)
if (m_range.layers() > 0) { if (m_range.layers() > 0) {
layer_t layerFirst, layerLast; layer_t layerFirst, layerLast;
if (selectedLayersBounds(selectedLayers(), if (selectedLayersBounds(m_range.selectedLayers(),
&layerFirst, &layerLast)) { &layerFirst, &layerLast)) {
layer_t layerIdx = m_clk.layer; layer_t layerIdx = m_clk.layer;
layerIdx = std::clamp(layerIdx, layerFirst, layerLast); layerIdx = std::clamp(layerIdx, layerFirst, layerLast);
@ -1331,6 +1345,8 @@ bool Timeline::onProcessMessage(Message* msg)
} }
case PART_TAG: { case PART_TAG: {
m_resizeTagData.reset(); // Reset resize info
Tag* tag = m_clk.getTag(); Tag* tag = m_clk.getTag();
if (tag) { if (tag) {
Params params; Params params;
@ -1403,9 +1419,13 @@ bool Timeline::onProcessMessage(Message* msg)
ContextWriter writer(m_context); ContextWriter writer(m_context);
Tx tx(writer, Strings::commands_FrameTagProperties()); Tx tx(writer, Strings::commands_FrameTagProperties());
tx(new cmd::SetTagRange( tx(new cmd::SetTagRange(
tag, tag,
(m_state == STATE_RESIZING_TAG_LEFT ? m_resizeTagData.from: tag->fromFrame()), (m_state == STATE_RESIZING_TAG_LEFT ?
(m_state == STATE_RESIZING_TAG_RIGHT ? m_resizeTagData.to: tag->toFrame()))); m_adapter->toRealFrame(m_resizeTagData.from) :
tag->fromFrame()),
(m_state == STATE_RESIZING_TAG_RIGHT ?
m_adapter->toRealFrame(m_resizeTagData.to) :
tag->toFrame())));
tx.commit(); tx.commit();
} }
catch (const base::Exception& e) { catch (const base::Exception& e) {
@ -1765,6 +1785,7 @@ void Timeline::onPaint(ui::PaintEvent& ev)
// Draw each visible layer. // Draw each visible layer.
DrawCelData data; DrawCelData data;
const view::fr_t realActiveFrame = m_adapter->toRealFrame(m_frame);
for (layer=lastLayer; layer>=firstLayer; --layer) { for (layer=lastLayer; layer>=firstLayer; --layer) {
{ {
IntersectClip clip(g, getLayerHeadersBounds()); IntersectClip clip(g, getLayerHeadersBounds());
@ -1785,12 +1806,19 @@ void Timeline::onPaint(ui::PaintEvent& ev)
continue; continue;
} }
// TODO Draw the set of cels by "column blocks" (set of
// consecutive frame columns)
// Get the first CelIterator to be drawn (it is the first cel with cel->frame >= first_frame) // Get the first CelIterator to be drawn (it is the first cel with cel->frame >= first_frame)
LayerImage* layerImagePtr = static_cast<LayerImage*>(layerPtr); LayerImage* layerImagePtr = static_cast<LayerImage*>(layerPtr);
data.begin = layerImagePtr->getCelBegin(); data.begin = layerImagePtr->getCelBegin();
data.end = layerImagePtr->getCelEnd(); data.end = layerImagePtr->getCelEnd();
data.it = layerImagePtr->findFirstCelIteratorAfter(firstFrame-1);
if (firstFrame > 0 && data.it != data.begin) const frame_t firstRealFrame(m_adapter->toRealFrame(firstFrame));
const frame_t lastRealFrame(m_adapter->toRealFrame(lastFrame));
data.it = layerImagePtr->findFirstCelIteratorAfter(firstRealFrame-1);
if (firstRealFrame > 0 && data.it != data.begin)
data.prevIt = data.it-1; data.prevIt = data.it-1;
else else
data.prevIt = data.end; data.prevIt = data.end;
@ -1801,7 +1829,7 @@ void Timeline::onPaint(ui::PaintEvent& ev)
data.lastLink = data.end; data.lastLink = data.end;
if (layerPtr == m_layer) { if (layerPtr == m_layer) {
data.activeIt = layerImagePtr->findCelIterator(m_frame); data.activeIt = layerImagePtr->findCelIterator(frame_t(realActiveFrame));
if (data.activeIt != data.end) { if (data.activeIt != data.end) {
data.firstLink = data.activeIt; data.firstLink = data.activeIt;
data.lastLink = data.activeIt; data.lastLink = data.activeIt;
@ -1814,7 +1842,7 @@ void Timeline::onPaint(ui::PaintEvent& ev)
--it2; --it2;
if ((*it2)->image()->id() == imageId) { if ((*it2)->image()->id() == imageId) {
data.firstLink = it2; data.firstLink = it2;
if ((*data.firstLink)->frame() < firstFrame) if ((*it2)->frame() < firstRealFrame)
break; break;
} }
} while (it2 != data.begin); } while (it2 != data.begin);
@ -1824,7 +1852,7 @@ void Timeline::onPaint(ui::PaintEvent& ev)
while (it2 != data.end) { while (it2 != data.end) {
if ((*it2)->image()->id() == imageId) { if ((*it2)->image()->id() == imageId) {
data.lastLink = it2; data.lastLink = it2;
if ((*data.lastLink)->frame() > lastFrame) if ((*it2)->frame() > lastRealFrame)
break; break;
} }
++it2; ++it2;
@ -1836,9 +1864,10 @@ void Timeline::onPaint(ui::PaintEvent& ev)
// Draw every visible cel for each layer. // Draw every visible cel for each layer.
for (frame=firstFrame; frame<=lastFrame; frame=col_t(frame+1)) { for (frame=firstFrame; frame<=lastFrame; frame=col_t(frame+1)) {
const view::fr_t realFrame = m_adapter->toRealFrame(frame);
Cel* cel = Cel* cel =
(data.it != data.end && (data.it != data.end &&
(*data.it)->frame() == frame ? *data.it: nullptr); (*data.it)->frame() == realFrame ? *data.it: nullptr);
drawCel(g, layer, frame, cel, &data); drawCel(g, layer, frame, cel, &data);
@ -2026,10 +2055,15 @@ void Timeline::onAddTag(DocEvent& ev)
updateScrollBars(); updateScrollBars();
layout(); layout();
} }
invalidate();
} }
void Timeline::onRemoveTag(DocEvent& ev) void Timeline::onRemoveTag(DocEvent& ev)
{ {
if (m_adapter && m_adapter->isViewingTag(ev.tag())) {
updateTimelineAdapter(true);
}
onAddTag(ev); onAddTag(ev);
} }
@ -2167,6 +2201,26 @@ void Timeline::getDrawableFrames(col_t* firstFrame, col_t* lastFrame)
*lastFrame = getFrameInXPos(viewScroll().x + availW); *lastFrame = getFrameInXPos(viewScroll().x + availW);
} }
// Gets the range of columns used by the tag. Returns false if the tag
// is not visible (or is partially visible) with the current timeline
// adapter/view.
bool Timeline::getTagFrames(const doc::Tag* tag, col_t* fromFrame, col_t* toFrame) const
{
ASSERT(tag);
*fromFrame = m_adapter->toColFrame(fr_t(tag->fromFrame()));
*toFrame = m_adapter->toColFrame(fr_t(tag->toFrame()));
if (m_resizeTagData.tag == tag->id()) {
*fromFrame = m_resizeTagData.from;
*toFrame = m_resizeTagData.to;
}
// TODO show partial tags
return (*fromFrame != kNoCol &&
*toFrame != kNoCol);
}
void Timeline::drawPart(ui::Graphics* g, const gfx::Rect& bounds, void Timeline::drawPart(ui::Graphics* g, const gfx::Rect& bounds,
const std::string* text, ui::Style* style, const std::string* text, ui::Style* style,
const bool is_active, const bool is_active,
@ -2265,19 +2319,19 @@ void Timeline::drawHeader(ui::Graphics* g)
NULL, styles.timelineBox(), false, false, false); NULL, styles.timelineBox(), false, false, false);
} }
void Timeline::drawHeaderFrame(ui::Graphics* g, col_t frame) void Timeline::drawHeaderFrame(ui::Graphics* g, col_t col)
{ {
bool is_active = isFrameActive(frame); bool is_active = isFrameActive(col);
bool is_hover = (m_hot.part == PART_HEADER_FRAME && m_hot.frame == frame); bool is_hover = (m_hot.part == PART_HEADER_FRAME && m_hot.frame == col);
bool is_clicked = (m_clk.part == PART_HEADER_FRAME && m_clk.frame == frame); bool is_clicked = (m_clk.part == PART_HEADER_FRAME && m_clk.frame == col);
gfx::Rect bounds = getPartBounds(Hit(PART_HEADER_FRAME, firstLayer(), frame)); gfx::Rect bounds = getPartBounds(Hit(PART_HEADER_FRAME, firstLayer(), col));
IntersectClip clip(g, bounds); IntersectClip clip(g, bounds);
if (!clip) if (!clip)
return; return;
// Draw the header for the layers. // Draw the header for the layers.
const fr_t realFrame = m_adapter->toRealFrame(frame); const fr_t frame = m_adapter->toRealFrame(col);
const int n = (docPref().timeline.firstFrame() + realFrame); const int n = (docPref().timeline.firstFrame() + frame);
std::string text = base::convert_to<std::string, int>(n % 100); std::string text = base::convert_to<std::string, int>(n % 100);
if (n >= 100 && (n % 100) < 10) if (n >= 100 && (n % 100) < 10)
text.insert(0, 1, '0'); text.insert(0, 1, '0');
@ -2430,7 +2484,7 @@ void Timeline::drawLayer(ui::Graphics* g, const int layerIdx)
} }
void Timeline::drawCel(ui::Graphics* g, void Timeline::drawCel(ui::Graphics* g,
const layer_t layerIndex, const col_t frame, const layer_t layerIndex, const col_t col,
const Cel* cel, const DrawCelData* data) const Cel* cel, const DrawCelData* data)
{ {
auto& styles = skinTheme()->styles; auto& styles = skinTheme()->styles;
@ -2438,18 +2492,20 @@ void Timeline::drawCel(ui::Graphics* g,
Image* image = (cel ? cel->image(): nullptr); Image* image = (cel ? cel->image(): nullptr);
bool is_hover = (m_hot.part == PART_CEL && bool is_hover = (m_hot.part == PART_CEL &&
m_hot.layer == layerIndex && m_hot.layer == layerIndex &&
m_hot.frame == frame); m_hot.frame == col);
const bool is_active = isCelActive(layerIndex, frame); const bool is_active = isCelActive(layerIndex, col);
const bool is_loosely_active = isCelLooselyActive(layerIndex, frame); const bool is_loosely_active = isCelLooselyActive(layerIndex, col);
const bool is_empty = (image == nullptr); const bool is_empty = (image == nullptr);
gfx::Rect bounds = getPartBounds(Hit(PART_CEL, layerIndex, frame)); gfx::Rect bounds = getPartBounds(Hit(PART_CEL, layerIndex, col));
gfx::Rect full_bounds = bounds; gfx::Rect full_bounds = bounds;
IntersectClip clip(g, bounds); IntersectClip clip(g, bounds);
if (!clip) if (!clip)
return; return;
const fr_t frame = m_adapter->toRealFrame(col);
// Draw background // Draw background
if (layer == m_layer && frame == m_frame) if (layer == m_layer && col == m_frame)
drawPart(g, bounds, nullptr, drawPart(g, bounds, nullptr,
m_range.enabled() ? styles.timelineFocusedCel(): m_range.enabled() ? styles.timelineFocusedCel():
styles.timelineSelectedCel(), false, is_hover, true); styles.timelineSelectedCel(), false, is_hover, true);
@ -2525,7 +2581,7 @@ void Timeline::drawCel(ui::Graphics* g,
// Draw decorators to link the activeCel with its links. // Draw decorators to link the activeCel with its links.
if (data && data->activeIt != data->end) if (data && data->activeIt != data->end)
drawCelLinkDecorators(g, full_bounds, cel, frame, is_loosely_active, is_hover, data); drawCelLinkDecorators(g, full_bounds, cel, col, is_loosely_active, is_hover, data);
// Draw 'z' if this cel has a custom z-index (non-zero) // Draw 'z' if this cel has a custom z-index (non-zero)
if (cel && cel->zIndex() != 0) { if (cel && cel->zIndex() != 0) {
@ -2617,7 +2673,7 @@ void Timeline::drawCelOverlay(ui::Graphics* g)
} }
void Timeline::drawCelLinkDecorators(ui::Graphics* g, const gfx::Rect& bounds, void Timeline::drawCelLinkDecorators(ui::Graphics* g, const gfx::Rect& bounds,
const Cel* cel, const col_t frame, const Cel* cel, const col_t col,
const bool is_active, const bool is_hover, const bool is_active, const bool is_hover,
const DrawCelData* data) const DrawCelData* data)
{ {
@ -2628,6 +2684,7 @@ void Timeline::drawCelLinkDecorators(ui::Graphics* g, const gfx::Rect& bounds,
ui::Style* style2 = nullptr; ui::Style* style2 = nullptr;
// Links at the left or right side // Links at the left or right side
fr_t frame = m_adapter->toRealFrame(col);
bool left = (data->firstLink != data->end ? frame > (*data->firstLink)->frame(): false); bool left = (data->firstLink != data->end ? frame > (*data->firstLink)->frame(): false);
bool right = (data->lastLink != data->end ? frame < (*data->lastLink)->frame(): false); bool right = (data->lastLink != data->end ? frame < (*data->lastLink)->frame(): false);
@ -2691,12 +2748,9 @@ void Timeline::drawTags(ui::Graphics* g)
} }
} }
col_t fromFrame = m_adapter->toColFrame(fr_t(tag->fromFrame())); col_t fromFrame, toFrame;
col_t toFrame = m_adapter->toColFrame(fr_t(tag->toFrame())); if (!getTagFrames(tag, &fromFrame, &toFrame))
if (m_resizeTagData.tag == tag->id()) { continue;
fromFrame = m_resizeTagData.from;
toFrame = m_resizeTagData.to;
}
gfx::Rect bounds1 = getPartBounds(Hit(PART_HEADER_FRAME, firstLayer(), fromFrame)); gfx::Rect bounds1 = getPartBounds(Hit(PART_HEADER_FRAME, firstLayer(), fromFrame));
gfx::Rect bounds2 = getPartBounds(Hit(PART_HEADER_FRAME, firstLayer(), toFrame)); gfx::Rect bounds2 = getPartBounds(Hit(PART_HEADER_FRAME, firstLayer(), toFrame));
@ -2711,19 +2765,19 @@ void Timeline::drawTags(ui::Graphics* g)
m_dropRange.type() == DocRange::kFrames) { m_dropRange.type() == DocRange::kFrames) {
switch (m_dropTarget.hhit) { switch (m_dropTarget.hhit) {
case DropTarget::Before: case DropTarget::Before:
if (m_dropRange.firstFrame() == tag->fromFrame()) { if (m_dropRange.firstFrame() == fromFrame) {
dx = +frameBoxWidth()/4; dx = +frameBoxWidth()/4;
dw = -frameBoxWidth()/4; dw = -frameBoxWidth()/4;
} }
else if (m_dropRange.firstFrame()-1 == tag->toFrame()) { else if (m_dropRange.firstFrame()-1 == toFrame) {
dw = -frameBoxWidth()/4; dw = -frameBoxWidth()/4;
} }
break; break;
case DropTarget::After: case DropTarget::After:
if (m_dropRange.lastFrame() == tag->toFrame()) { if (m_dropRange.lastFrame() == toFrame) {
dw = -frameBoxWidth()/4; dw = -frameBoxWidth()/4;
} }
else if (m_dropRange.lastFrame()+1 == tag->fromFrame()) { else if (m_dropRange.lastFrame()+1 == fromFrame) {
dx = +frameBoxWidth()/4; dx = +frameBoxWidth()/4;
dw = -frameBoxWidth()/4; dw = -frameBoxWidth()/4;
} }
@ -2839,10 +2893,9 @@ void Timeline::drawRangeOutline(ui::Graphics* g)
theme()->paintWidgetPart( theme()->paintWidgetPart(
g, styles.timelineRangeOutline(), bounds, info); g, styles.timelineRangeOutline(), bounds, info);
Range drop = m_dropRange; gfx::Rect dropBounds = getRangeBounds(m_dropRange);
gfx::Rect dropBounds = getRangeBounds(drop);
switch (drop.type()) { switch (m_dropRange.type()) {
case Range::kCels: { case Range::kCels: {
dropBounds = dropBounds.enlarge(outlineWidth()); dropBounds = dropBounds.enlarge(outlineWidth());
@ -2857,7 +2910,7 @@ void Timeline::drawRangeOutline(ui::Graphics* g)
if (m_dropTarget.hhit == DropTarget::Before) if (m_dropTarget.hhit == DropTarget::Before)
dropBounds.x -= w/2; dropBounds.x -= w/2;
else if (drop == m_range) else if (m_dropRange == m_range)
dropBounds.x = dropBounds.x + getRangeBounds(m_range).w - w/2; dropBounds.x = dropBounds.x + getRangeBounds(m_range).w - w/2;
else else
dropBounds.x = dropBounds.x + dropBounds.w - w/2; dropBounds.x = dropBounds.x + dropBounds.w - w/2;
@ -2875,7 +2928,7 @@ void Timeline::drawRangeOutline(ui::Graphics* g)
if (m_dropTarget.vhit == DropTarget::Top) if (m_dropTarget.vhit == DropTarget::Top)
dropBounds.y -= h/2; dropBounds.y -= h/2;
else if (drop == m_range) else if (m_dropRange == m_range)
dropBounds.y = dropBounds.y + getRangeBounds(m_range).h - h/2; dropBounds.y = dropBounds.y + getRangeBounds(m_range).h - h/2;
else else
dropBounds.y = dropBounds.y + dropBounds.h - h/2; dropBounds.y = dropBounds.y + dropBounds.h - h/2;
@ -3087,12 +3140,8 @@ gfx::Rect Timeline::getPartBounds(const Hit& hit) const
case PART_TAG: { case PART_TAG: {
Tag* tag = hit.getTag(); Tag* tag = hit.getTag();
if (tag) { if (tag) {
col_t fromFrame = m_adapter->toColFrame(fr_t(tag->fromFrame())); col_t fromFrame, toFrame;
col_t toFrame = m_adapter->toColFrame(fr_t(tag->toFrame())); getTagFrames(tag, &fromFrame, &toFrame);
if (m_resizeTagData.tag == tag->id()) {
fromFrame = m_resizeTagData.from;
toFrame = m_resizeTagData.to;
}
gfx::Rect bounds1 = getPartBounds(Hit(PART_HEADER_FRAME, firstLayer(), fromFrame)); gfx::Rect bounds1 = getPartBounds(Hit(PART_HEADER_FRAME, firstLayer(), fromFrame));
gfx::Rect bounds2 = getPartBounds(Hit(PART_HEADER_FRAME, firstLayer(), toFrame)); gfx::Rect bounds2 = getPartBounds(Hit(PART_HEADER_FRAME, firstLayer(), toFrame));
@ -3326,28 +3375,30 @@ void Timeline::regenerateTagBands()
const bool oldEmptyTagBand = m_tagBand.empty(); const bool oldEmptyTagBand = m_tagBand.empty();
// TODO improve this implementation // TODO improve this implementation
std::vector<unsigned char> tagsPerFrame(m_sprite->totalFrames(), 0); std::vector<unsigned char> tagsPerFrame(m_adapter->totalFrames(), 0);
std::vector<Tag*> bands(4, nullptr); std::vector<Tag*> bands(4, nullptr);
m_tagBand.clear(); m_tagBand.clear();
for (Tag* tag : m_sprite->tags()) { for (Tag* tag : m_sprite->tags()) {
frame_t f = tag->fromFrame(); col_t fromFrame, toFrame;
if (!getTagFrames(tag, &fromFrame, &toFrame))
continue; // Ignore tags that are not inside the timeline adapter/view
int b=0; int b=0;
for (; b<int(bands.size()); ++b) { for (; b<int(bands.size()); ++b) {
if (!bands[b] || if (!bands[b] ||
tag->fromFrame() > calcTagVisibleToFrame(bands[b])) { fromFrame > calcTagVisibleToFrame(bands[b])) {
bands[b] = tag; bands[b] = tag;
m_tagBand[tag] = b; m_tagBand[tag] = b;
break; break;
} }
} }
if (b == int(bands.size())) if (b == int(bands.size()))
m_tagBand[tag] = tagsPerFrame[f]; m_tagBand[tag] = tagsPerFrame[fromFrame];
frame_t toFrame = calcTagVisibleToFrame(tag); toFrame = calcTagVisibleToFrame(tag);
if (toFrame >= frame_t(tagsPerFrame.size())) if (toFrame >= col_t(tagsPerFrame.size()))
tagsPerFrame.resize(toFrame+1, 0); tagsPerFrame.resize(toFrame+1, 0);
for (; f<=toFrame; ++f) { for (col_t f=fromFrame; f<=toFrame; f=col_t(f+1)) {
ASSERT(f < frame_t(tagsPerFrame.size())); ASSERT(f < frame_t(tagsPerFrame.size()));
if (tagsPerFrame[f] < 255) if (tagsPerFrame[f] < 255)
++tagsPerFrame[f]; ++tagsPerFrame[f];
@ -3478,6 +3529,10 @@ Timeline::Hit Timeline::hitTest(ui::Message* msg, const gfx::Point& mousePos)
// Mouse in frame tags // Mouse in frame tags
if (hit.part == PART_NOTHING) { if (hit.part == PART_NOTHING) {
for (Tag* tag : m_sprite->tags()) { for (Tag* tag : m_sprite->tags()) {
col_t fromFrame, toFrame;
if (!getTagFrames(tag, &fromFrame, &toFrame))
continue;
const int band = m_tagBand[tag]; const int band = m_tagBand[tag];
// Skip unfocused bands // Skip unfocused bands
@ -3495,8 +3550,8 @@ Timeline::Hit Timeline::hitTest(ui::Message* msg, const gfx::Point& mousePos)
} }
// Check if we are in the left/right handles to resize the tag // Check if we are in the left/right handles to resize the tag
else { else {
gfx::Rect bounds1 = getPartBounds(Hit(PART_HEADER_FRAME, firstLayer(), m_adapter->toColFrame(fr_t(tag->fromFrame())))); gfx::Rect bounds1 = getPartBounds(Hit(PART_HEADER_FRAME, firstLayer(), fromFrame));
gfx::Rect bounds2 = getPartBounds(Hit(PART_HEADER_FRAME, firstLayer(), m_adapter->toColFrame(fr_t(tag->toFrame())))); gfx::Rect bounds2 = getPartBounds(Hit(PART_HEADER_FRAME, firstLayer(), toFrame));
gfx::Rect bounds = bounds1.createUnion(bounds2); gfx::Rect bounds = bounds1.createUnion(bounds2);
bounds.h = bounds.y2() - tagBounds.y2(); bounds.h = bounds.y2() - tagBounds.y2();
bounds.y = tagBounds.y2(); bounds.y = tagBounds.y2();
@ -3822,15 +3877,16 @@ void Timeline::updateStatusBar(ui::Message* msg)
sb->showDefaultText(); sb->showDefaultText();
} }
void Timeline::updateStatusBarForFrame(const col_t frame, void Timeline::updateStatusBarForFrame(const col_t col,
const Tag* tag, const Tag* tag,
const Cel* cel) const Cel* cel)
{ {
if (!m_sprite) if (!m_sprite || !m_adapter)
return; return;
std::string buf; std::string buf;
frame_t base = docPref().timeline.firstFrame(); frame_t base = docPref().timeline.firstFrame();
const fr_t frame = m_adapter->toRealFrame(col);
frame_t firstFrame = frame; frame_t firstFrame = frame;
frame_t lastFrame = frame; frame_t lastFrame = frame;
@ -3840,8 +3896,8 @@ void Timeline::updateStatusBarForFrame(const col_t frame,
} }
else if (m_range.enabled() && else if (m_range.enabled() &&
m_range.frames() > 1) { m_range.frames() > 1) {
firstFrame = m_range.firstFrame(); firstFrame = m_adapter->toRealFrame(col_t(m_range.firstFrame()));
lastFrame = m_range.lastFrame(); lastFrame = m_adapter->toRealFrame(col_t(m_range.lastFrame()));
} }
buf += fmt::format(":frame: {}", buf += fmt::format(":frame: {}",
@ -3853,7 +3909,7 @@ void Timeline::updateStatusBarForFrame(const col_t frame,
} }
buf += fmt::format(" :clock: {}", buf += fmt::format(" :clock: {}",
human_readable_time(m_adapter->frameDuration(frame))); human_readable_time(m_adapter->frameDuration(col)));
if (firstFrame != lastFrame) { if (firstFrame != lastFrame) {
buf += fmt::format(" [{}]", buf += fmt::format(" [{}]",
tag ? tag ?
@ -4092,9 +4148,9 @@ bool Timeline::isCelLooselyActive(const layer_t layerIdx, const col_t frame) con
void Timeline::dropRange(DropOp op) void Timeline::dropRange(DropOp op)
{ {
bool copy = (op == Timeline::kCopy); bool copy = (op == Timeline::kCopy);
Range newFromRange; VirtualRange newFromRange;
DocRangePlace place = kDocRangeAfter; DocRangePlace place = kDocRangeAfter;
Range dropRange = m_dropRange; RealRange dropRange = view::to_real_range(m_adapter.get(), m_dropRange);
bool outside = m_dropTarget.outside; bool outside = m_dropTarget.outside;
switch (m_range.type()) { switch (m_range.type()) {
@ -4130,13 +4186,14 @@ void Timeline::dropRange(DropOp op)
try { try {
TagsHandling tagsHandling = (outside ? kFitOutsideTags: TagsHandling tagsHandling = (outside ? kFitOutsideTags:
kFitInsideTags); kFitInsideTags);
const RealRange realRange = this->realRange();
invalidateRange(); invalidateRange();
if (copy) if (copy)
newFromRange = copy_range(m_document, m_range, dropRange, newFromRange = copy_range(m_document, realRange, dropRange,
place, tagsHandling); place, tagsHandling);
else else
newFromRange = move_range(m_document, m_range, dropRange, newFromRange = move_range(m_document, realRange, dropRange,
place, tagsHandling); place, tagsHandling);
// If we drop a cel in the same frame (but in another layer), // If we drop a cel in the same frame (but in another layer),
@ -4144,6 +4201,7 @@ void Timeline::dropRange(DropOp op)
// all views. // all views.
m_document->notifyGeneralUpdate(); m_document->notifyGeneralUpdate();
newFromRange = view::to_virtual_range(m_adapter.get(), newFromRange);
moveRange(newFromRange); moveRange(newFromRange);
invalidateRange(); invalidateRange();
@ -4375,8 +4433,8 @@ double Timeline::zoom() const
return 1.0; return 1.0;
} }
// Returns the last frame where the frame tag (or frame tag label) // Returns the last frame where the frame tag (or tag label) is
// is visible in the timeline. // visible in the timeline.
view::col_t Timeline::calcTagVisibleToFrame(Tag* tag) const view::col_t Timeline::calcTagVisibleToFrame(Tag* tag) const
{ {
col_t frame = col_t frame =
@ -4793,6 +4851,13 @@ void Timeline::setSeparatorX(int newValue)
m_separator_x = std::max(0, newValue) / guiscale(); m_separator_x = std::max(0, newValue) / guiscale();
} }
// Re-constructs the timeline adapter
void Timeline::updateTimelineAdapter(bool allTags)
{
m_adapter = std::make_unique<view::FullSpriteTimelineAdapter>(m_sprite);
invalidate();
}
//static //static
gfx::Color Timeline::highlightColor(const gfx::Color color) gfx::Color Timeline::highlightColor(const gfx::Color color)
{ {

View File

@ -10,7 +10,6 @@
#pragma once #pragma once
#include "app/doc_observer.h" #include "app/doc_observer.h"
#include "app/doc_range.h"
#include "app/docs_observer.h" #include "app/docs_observer.h"
#include "app/loop_tag.h" #include "app/loop_tag.h"
#include "app/pref/preferences.h" #include "app/pref/preferences.h"
@ -32,6 +31,7 @@
#include "ui/scroll_bar.h" #include "ui/scroll_bar.h"
#include "ui/timer.h" #include "ui/timer.h"
#include "ui/widget.h" #include "ui/widget.h"
#include "view/range.h"
#include "view/timeline_adapter.h" #include "view/timeline_adapter.h"
#include <memory> #include <memory>
@ -74,7 +74,9 @@ namespace app {
public InputChainElement, public InputChainElement,
public TagProvider { public TagProvider {
public: public:
using Range = DocRange; using Range = view::Range;
using RealRange = view::RealRange;
using VirtualRange = view::VirtualRange;
using fr_t = view::fr_t; using fr_t = view::fr_t;
using col_t = view::col_t; using col_t = view::col_t;
static constexpr const auto kNoCol = view::kNoCol; static constexpr const auto kNoCol = view::kNoCol;
@ -117,13 +119,16 @@ namespace app {
// The range is specified in "virtual frames" (not real sprite // The range is specified in "virtual frames" (not real sprite
// frames, we'll have to do a conversion each time we want to use // frames, we'll have to do a conversion each time we want to use
// this range with the sprite). // this range with the sprite).
Range range() const { return m_range; } VirtualRange virtualRange() const { return m_range; }
const SelectedLayers& selectedLayers() const { return m_range.selectedLayers(); } bool isRangeEnabled() const { return m_range.enabled(); }
const SelectedFrames& selectedFrames() const { return m_range.selectedFrames(); }
// Returns the range in "real sprite frames."
RealRange realRange() const;
void prepareToMoveRange(); void prepareToMoveRange();
void moveRange(const Range& range); void moveRange(const VirtualRange& range);
void setRange(const Range& range); void setVirtualRange(const VirtualRange& range);
void setRealRange(const RealRange& range);
void activateClipboardRange(); void activateClipboardRange();
@ -294,6 +299,7 @@ namespace app {
void setCursor(ui::Message* msg, const Hit& hit); void setCursor(ui::Message* msg, const Hit& hit);
void getDrawableLayers(layer_t* firstLayer, layer_t* lastLayer); void getDrawableLayers(layer_t* firstLayer, layer_t* lastLayer);
void getDrawableFrames(col_t* firstFrame, col_t* lastFrame); void getDrawableFrames(col_t* firstFrame, col_t* lastFrame);
bool getTagFrames(const doc::Tag* tag, col_t* fromFrame, col_t* toFrame) const;
void drawPart(ui::Graphics* g, const gfx::Rect& bounds, void drawPart(ui::Graphics* g, const gfx::Rect& bounds,
const std::string* text, const std::string* text,
ui::Style* style, ui::Style* style,
@ -303,13 +309,13 @@ namespace app {
const bool is_disabled = false); const bool is_disabled = false);
void drawTop(ui::Graphics* g); void drawTop(ui::Graphics* g);
void drawHeader(ui::Graphics* g); void drawHeader(ui::Graphics* g);
void drawHeaderFrame(ui::Graphics* g, const col_t frame); void drawHeaderFrame(ui::Graphics* g, const col_t col);
void drawLayer(ui::Graphics* g, const layer_t layerIdx); void drawLayer(ui::Graphics* g, const layer_t layerIdx);
void drawCel(ui::Graphics* g, void drawCel(ui::Graphics* g,
const layer_t layerIdx, const col_t frame, const layer_t layerIdx, const col_t col,
const Cel* cel, const DrawCelData* data); const Cel* cel, const DrawCelData* data);
void drawCelLinkDecorators(ui::Graphics* g, const gfx::Rect& bounds, void drawCelLinkDecorators(ui::Graphics* g, const gfx::Rect& bounds,
const Cel* cel, const col_t frame, const Cel* cel, const col_t col,
const bool is_active, const bool is_hover, const bool is_active, const bool is_hover,
const DrawCelData* data); const DrawCelData* data);
void drawTags(ui::Graphics* g); void drawTags(ui::Graphics* g);
@ -409,6 +415,7 @@ namespace app {
int separatorX() const; int separatorX() const;
void setSeparatorX(int newValue); void setSeparatorX(int newValue);
void updateTimelineAdapter(bool allTags);
static gfx::Color highlightColor(const gfx::Color color); static gfx::Color highlightColor(const gfx::Color color);
@ -424,9 +431,9 @@ namespace app {
Layer* m_layer; Layer* m_layer;
col_t m_frame; col_t m_frame;
int m_rangeLocks; int m_rangeLocks;
Range m_range; VirtualRange m_range;
Range m_startRange; VirtualRange m_startRange;
Range m_dropRange; VirtualRange m_dropRange;
State m_state; State m_state;
// Version of the sprite before executing a command. Used to check // Version of the sprite before executing a command. Used to check
@ -493,13 +500,13 @@ namespace app {
} }
void reset(const view::TimelineAdapter& adapter, void reset(const view::TimelineAdapter& adapter,
const doc::ObjectId tagId) { const doc::ObjectId tagId) {
if (auto tag = doc::get<doc::Tag>(tagId)) { if (auto t = doc::get<doc::Tag>(tagId)) {
this->tag = tagId; tag = tagId;
this->from = adapter.toColFrame(fr_t(tag->fromFrame())); from = adapter.toColFrame(fr_t(t->fromFrame()));
this->to = adapter.toColFrame(fr_t(tag->toFrame())); to = adapter.toColFrame(fr_t(t->toFrame()));
} }
else { else {
this->tag = doc::NullId; tag = doc::NullId;
} }
} }
} m_resizeTagData; } m_resizeTagData;

View File

@ -176,13 +176,13 @@ void UIContext::onSetActiveFrame(const doc::frame_t frame)
Context::onSetActiveFrame(frame); Context::onSetActiveFrame(frame);
} }
void UIContext::onSetRange(const DocRange& range) void UIContext::onSetRange(const view::RealRange& range)
{ {
Timeline* timeline = Timeline* timeline =
(App::instance()->mainWindow() ? (App::instance()->mainWindow() ?
App::instance()->mainWindow()->getTimeline(): nullptr); App::instance()->mainWindow()->getTimeline(): nullptr);
if (timeline) { if (timeline) {
timeline->setRange(range); timeline->setRealRange(range);
} }
else if (!isUIAvailable()) { else if (!isUIAvailable()) {
Context::onSetRange(range); Context::onSetRange(range);
@ -353,8 +353,8 @@ void UIContext::onGetActiveSite(Site* site) const
Timeline* timeline = App::instance()->timeline(); Timeline* timeline = App::instance()->timeline();
if (timeline && if (timeline &&
timeline->isVisible() && timeline->isVisible() &&
timeline->range().enabled()) { timeline->isRangeEnabled()) {
site->range(timeline->range()); site->range(timeline->realRange());
} }
else { else {
ColorBar* colorBar = ColorBar::instance(); ColorBar* colorBar = ColorBar::instance();

View File

@ -4,7 +4,8 @@
add_library(view-lib add_library(view-lib
cels.cpp cels.cpp
layers.cpp layers.cpp
range.cpp) range.cpp
utils.cpp)
target_link_libraries(view-lib target_link_libraries(view-lib
doc-lib doc-lib

View File

@ -285,29 +285,37 @@ void Range::setType(const Type type)
} }
} }
void Range::setSelectedLayers(const SelectedLayers& layers) void Range::setSelectedLayers(const SelectedLayers& layers,
const bool touchFlags)
{ {
if (layers.empty()) { if (layers.empty()) {
m_type = kNone; if (touchFlags)
m_type = kNone;
m_selectedLayers.clear(); m_selectedLayers.clear();
return; return;
} }
m_type = kLayers; if (touchFlags) {
m_flags |= kLayers; m_type = kLayers;
m_flags |= kLayers;
}
m_selectedLayers = layers; m_selectedLayers = layers;
} }
void Range::setSelectedFrames(const SelectedFrames& frames) void Range::setSelectedFrames(const SelectedFrames& frames,
const bool touchFlags)
{ {
if (frames.empty()) { if (frames.empty()) {
m_type = kNone; if (touchFlags)
m_type = kNone;
m_selectedFrames.clear(); m_selectedFrames.clear();
return; return;
} }
m_type = kFrames; if (touchFlags) {
m_flags |= kFrames; m_type = kFrames;
m_flags |= kFrames;
}
m_selectedFrames = frames; m_selectedFrames = frames;
} }

View File

@ -43,8 +43,10 @@ namespace view {
const doc::SelectedFrames& selectedFrames() const { return m_selectedFrames; } const doc::SelectedFrames& selectedFrames() const { return m_selectedFrames; }
void setType(const Type type); void setType(const Type type);
void setSelectedLayers(const doc::SelectedLayers& layers); void setSelectedLayers(const doc::SelectedLayers& layers,
void setSelectedFrames(const doc::SelectedFrames& frames); const bool touchFlags = true);
void setSelectedFrames(const doc::SelectedFrames& frames,
const bool touchFlags = true);
void displace(const doc::layer_t layerDelta, void displace(const doc::layer_t layerDelta,
const doc::frame_t frameDelta); const doc::frame_t frameDelta);
@ -95,6 +97,13 @@ namespace view {
doc::frame_t m_selectingFromFrame; doc::frame_t m_selectingFromFrame;
}; };
// TODO We should make these types strongly-typed and not just aliases.
// E.g.
// using VirtualRange = RangeT<col_t>;
// using RealRange = RangeT<fr_t>;
using VirtualRange = Range;
using RealRange = Range;
} // namespace view } // namespace view
#endif // VIEW_RANGE_H_INCLUDED #endif // VIEW_RANGE_H_INCLUDED

View File

@ -9,6 +9,7 @@
#pragma once #pragma once
#include "doc/sprite.h" #include "doc/sprite.h"
#include "doc/tag.h"
#include "view/frames.h" #include "view/frames.h"
namespace view { namespace view {
@ -35,6 +36,12 @@ public:
// frame of the sprite. Returns -1 if the frame is not visible given // frame of the sprite. Returns -1 if the frame is not visible given
// the current timeline filters/hidden frames. // the current timeline filters/hidden frames.
virtual col_t toColFrame(fr_t frame) const = 0; virtual col_t toColFrame(fr_t frame) const = 0;
// Returns true if this tag is being used for this specific timeline
// adapter/view. E.g. if a tag is going to be removed and this
// timeline view is using that tag, we must switch to other kind of
// timeline view that doesn't use this tag anymore.
virtual bool isViewingTag(doc::Tag* tag) const = 0;
}; };
// Represents the default timeline view where the whole sprite is // Represents the default timeline view where the whole sprite is
@ -46,10 +53,38 @@ public:
int frameDuration(col_t frame) const override { return m_sprite->frameDuration(frame); } int frameDuration(col_t frame) const override { return m_sprite->frameDuration(frame); }
fr_t toRealFrame(col_t frame) const override { return fr_t(frame); } fr_t toRealFrame(col_t frame) const override { return fr_t(frame); }
col_t toColFrame(fr_t frame) const override { return col_t(frame); } col_t toColFrame(fr_t frame) const override { return col_t(frame); }
bool isViewingTag(doc::Tag*) const override { return false; }
private: private:
doc::Sprite* m_sprite = nullptr; doc::Sprite* m_sprite = nullptr;
}; };
// Represents an alternative timeline to show only the specified tag.
class ShowTagTimelineAdapter : public TimelineAdapter {
public:
ShowTagTimelineAdapter(doc::Sprite* sprite,
doc::Tag* tag) : m_sprite(sprite)
, m_tag(tag) { }
col_t totalFrames() const override { return col_t(m_tag->frames()); }
int frameDuration(col_t frame) const override {
return m_sprite->frameDuration(doc::frame_t(toRealFrame(frame)));
}
fr_t toRealFrame(col_t frame) const override {
return fr_t(frame + m_tag->fromFrame());
}
col_t toColFrame(fr_t frame) const override {
if (m_tag->contains(frame))
return col_t(frame - m_tag->fromFrame());
else
return kNoCol;
}
bool isViewingTag(doc::Tag* tag) const override {
return m_tag == tag;
}
private:
doc::Sprite* m_sprite = nullptr;
doc::Tag* m_tag = nullptr;
};
} // namespace view } // namespace view
#endif // VIEW_TIMELINE_ADAPTER_H_INCLUDED #endif // VIEW_TIMELINE_ADAPTER_H_INCLUDED

47
src/view/utils.cpp Normal file
View File

@ -0,0 +1,47 @@
// Aseprite View Library
// Copyright (c) 2023 Igara Studio S.A.
//
// This program is distributed under the terms of
// the End-User License Agreement for Aseprite.
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include "view/utils.h"
#include "doc/selected_frames.h"
#include "view/timeline_adapter.h"
namespace view {
VirtualRange to_virtual_range(const TimelineAdapter* adapter,
const RealRange& realRange)
{
VirtualRange range(realRange);
if (range.enabled()) {
doc::SelectedFrames frames;
for (auto frame : realRange.selectedFrames()) {
col_t col = adapter->toColFrame(fr_t(frame));
if (col != kNoCol)
frames.insert(doc::frame_t(col));
}
range.setSelectedFrames(frames, false);
}
return range;
}
RealRange to_real_range(const TimelineAdapter* adapter,
const VirtualRange& virtualRange)
{
RealRange range(virtualRange);
if (range.enabled()) {
doc::SelectedFrames frames;
for (auto frame : virtualRange.selectedFrames())
frames.insert(doc::frame_t(adapter->toRealFrame(col_t(frame))));
range.setSelectedFrames(frames, false);
}
return range;
}
} // namespace view

25
src/view/utils.h Normal file
View File

@ -0,0 +1,25 @@
// Aseprite View Library
// Copyright (c) 2023 Igara Studio S.A.
//
// This program is distributed under the terms of
// the End-User License Agreement for Aseprite.
#ifndef VIEW_UTILS_H_INCLUDED
#define VIEW_UTILS_H_INCLUDED
#pragma once
#include "view/range.h"
namespace view {
class TimelineAdapter;
VirtualRange to_virtual_range(const TimelineAdapter* adapter,
const RealRange& realRange);
RealRange to_real_range(const TimelineAdapter* adapter,
const VirtualRange& virtualRange);
} // namespace view
#endif // VIEW_RANGE_H_INCLUDED