mirror of
https://github.com/aseprite/aseprite.git
synced 2025-03-01 10:13:22 +00:00
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:
parent
8a2df3b01d
commit
be17c48324
@ -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 };
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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;
|
||||
};
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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())
|
||||
|
@ -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()
|
||||
|
@ -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
|
||||
|
||||
|
@ -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());
|
||||
|
@ -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();
|
||||
|
@ -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();
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
@ -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()) {
|
||||
|
@ -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);
|
||||
|
@ -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) {
|
||||
|
@ -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)
|
||||
|
@ -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();
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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)
|
||||
{
|
||||
|
@ -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;
|
||||
|
@ -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();
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
|
@ -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
47
src/view/utils.cpp
Normal 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
25
src/view/utils.h
Normal 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
|
Loading…
x
Reference in New Issue
Block a user