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
// Copyright (C) 2023 Igara Studio SA
// Copyright (C) 2001-2018 David Capello
//
// This program is distributed under the terms of
@ -43,6 +44,8 @@ namespace app {
virtual size_t onMemSize() const;
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;
#if _DEBUG
enum class State { NotExecuted, Executed, Undone, Redone };

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -113,12 +113,12 @@ void SelectPaletteColorsCommand::onExecute(Context* context)
{
Site site = context->activeSite();
Sprite* sprite = site.sprite();
DocRange range = site.range();
view::RealRange range = site.range();
SelectedFrames selectedFrames;
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:
range.startRange(site.layer(), 0, DocRange::Type::kFrames);
range.startRange(site.layer(), 0, view::Range::Type::kFrames);
range.endRange(site.layer(), sprite->lastFrame());
selectedFrames = range.selectedFrames();
selectedLayers.selectAllLayers(sprite->root());

View File

@ -80,7 +80,7 @@ void SetLoopSectionCommand::onExecute(Context* ctx)
switch (m_action) {
case Action::Auto: {
auto range = App::instance()->timeline()->range();
const view::RealRange& range = ctx->range();
if (range.enabled() && (range.frames() > 1)) {
begin = range.selectedFrames().firstFrame();
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
// the undo and we can deserialize them.
if (docRangeStream) {
Timeline* timeline = App::instance()->timeline();
if (timeline) {
DocRange docRange;
if (docRange.read(*docRangeStream))
timeline->setRange(docRange);
}
view::Range docRange;
if (docRange.read(*docRangeStream))
context->setRange(docRange);
}
document->generateMaskBoundaries();

View File

@ -91,6 +91,14 @@ Doc* Context::activeDocument() const
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)
{
onSetActiveDocument(document, true);
@ -106,7 +114,7 @@ void Context::setActiveFrame(const doc::frame_t frame)
onSetActiveFrame(frame);
}
void Context::setRange(const DocRange& range)
void Context::setRange(const view::RealRange& range)
{
onSetRange(range);
}
@ -299,7 +307,7 @@ void Context::onSetActiveFrame(const doc::frame_t frame)
notifyActiveSiteChanged();
}
void Context::onSetRange(const DocRange& range)
void Context::onSetRange(const view::RealRange& range)
{
if (m_lastSelectedDoc)
activeSiteHandler()->setRangeInDoc(m_lastSelectedDoc, range);

View File

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

View File

@ -1,5 +1,5 @@
// Aseprite
// Copyright (C) 2019-2022 Igara Studio S.A.
// Copyright (C) 2019-2023 Igara Studio S.A.
// Copyright (C) 2001-2018 David Capello
//
// 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);
}
void Site::range(const DocRange& range)
void Site::range(const view::RealRange& range)
{
m_range = range;
switch (range.type()) {
case DocRange::kCels: m_focus = Site::InCels; break;
case DocRange::kFrames: m_focus = Site::InFrames; break;
case DocRange::kLayers: m_focus = Site::InLayers; break;
case view::Range::kCels: m_focus = Site::InCels; break;
case view::Range::kFrames: m_focus = Site::InFrames; break;
case view::Range::kLayers: m_focus = Site::InLayers; break;
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -4,7 +4,8 @@
add_library(view-lib
cels.cpp
layers.cpp
range.cpp)
range.cpp
utils.cpp)
target_link_libraries(view-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()) {
m_type = kNone;
if (touchFlags)
m_type = kNone;
m_selectedLayers.clear();
return;
}
m_type = kLayers;
m_flags |= kLayers;
if (touchFlags) {
m_type = kLayers;
m_flags |= kLayers;
}
m_selectedLayers = layers;
}
void Range::setSelectedFrames(const SelectedFrames& frames)
void Range::setSelectedFrames(const SelectedFrames& frames,
const bool touchFlags)
{
if (frames.empty()) {
m_type = kNone;
if (touchFlags)
m_type = kNone;
m_selectedFrames.clear();
return;
}
m_type = kFrames;
m_flags |= kFrames;
if (touchFlags) {
m_type = kFrames;
m_flags |= kFrames;
}
m_selectedFrames = frames;
}

View File

@ -43,8 +43,10 @@ namespace view {
const doc::SelectedFrames& selectedFrames() const { return m_selectedFrames; }
void setType(const Type type);
void setSelectedLayers(const doc::SelectedLayers& layers);
void setSelectedFrames(const doc::SelectedFrames& frames);
void setSelectedLayers(const doc::SelectedLayers& layers,
const bool touchFlags = true);
void setSelectedFrames(const doc::SelectedFrames& frames,
const bool touchFlags = true);
void displace(const doc::layer_t layerDelta,
const doc::frame_t frameDelta);
@ -95,6 +97,13 @@ namespace view {
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
#endif // VIEW_RANGE_H_INCLUDED

View File

@ -9,6 +9,7 @@
#pragma once
#include "doc/sprite.h"
#include "doc/tag.h"
#include "view/frames.h"
namespace view {
@ -35,6 +36,12 @@ public:
// frame of the sprite. Returns -1 if the frame is not visible given
// the current timeline filters/hidden frames.
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
@ -46,10 +53,38 @@ public:
int frameDuration(col_t frame) const override { return m_sprite->frameDuration(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); }
bool isViewingTag(doc::Tag*) const override { return false; }
private:
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
#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