From be17c4832405be44193a4d24268c38da80369743 Mon Sep 17 00:00:00 2001 From: David Capello Date: Thu, 8 Jun 2023 19:00:49 -0300 Subject: [PATCH] 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. --- src/app/cmd.h | 3 + src/app/cmd_transaction.cpp | 24 +-- src/app/cmd_transaction.h | 8 +- src/app/commands/cmd_cel_properties.cpp | 6 +- src/app/commands/cmd_layer_lock.cpp | 6 +- src/app/commands/cmd_layer_properties.cpp | 8 +- src/app/commands/cmd_layer_visibility.cpp | 7 +- src/app/commands/cmd_new_frame.cpp | 2 +- src/app/commands/cmd_new_frame_tag.cpp | 11 +- src/app/commands/cmd_reverse_frames.cpp | 5 +- src/app/commands/cmd_select_palette.cpp | 6 +- src/app/commands/cmd_set_loop_section.cpp | 2 +- src/app/commands/cmd_undo.cpp | 9 +- src/app/context.cpp | 12 +- src/app/context.h | 11 +- src/app/site.cpp | 10 +- src/app/site.h | 8 +- src/app/tx.h | 6 +- src/app/ui/editor/editor.cpp | 4 +- src/app/ui/editor/moving_cel_state.cpp | 2 +- src/app/ui/editor/moving_slice_state.cpp | 2 +- src/app/ui/editor/pixels_movement.cpp | 2 +- src/app/ui/editor/standby_state.cpp | 3 +- src/app/ui/keyboard_shortcuts.cpp | 13 +- src/app/ui/timeline/timeline.cpp | 203 ++++++++++++++-------- src/app/ui/timeline/timeline.h | 43 +++-- src/app/ui_context.cpp | 8 +- src/view/CMakeLists.txt | 3 +- src/view/range.cpp | 24 ++- src/view/range.h | 13 +- src/view/timeline_adapter.h | 35 ++++ src/view/utils.cpp | 47 +++++ src/view/utils.h | 25 +++ 33 files changed, 381 insertions(+), 190 deletions(-) create mode 100644 src/view/utils.cpp create mode 100644 src/view/utils.h diff --git a/src/app/cmd.h b/src/app/cmd.h index 6af33ab35..381236abb 100644 --- a/src/app/cmd.h +++ b/src/app/cmd.h @@ -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 }; diff --git a/src/app/cmd_transaction.cpp b/src/app/cmd_transaction.cpp index 39c52ee9e..96d7578f2 100644 --- a/src/app/cmd_transaction.cpp +++ b/src/app/cmd_transaction.cpp @@ -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 diff --git a/src/app/cmd_transaction.h b/src/app/cmd_transaction.h index 1de523e26..05ccb8e67 100644 --- a/src/app/cmd_transaction.h +++ b/src/app/cmd_transaction.h @@ -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 #include @@ -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; diff --git a/src/app/commands/cmd_cel_properties.cpp b/src/app/commands/cmd_cel_properties.cpp index 89f6d01aa..619b96d01 100644 --- a/src/app/commands/cmd_cel_properties.cpp +++ b/src/app/commands/cmd_cel_properties.cpp @@ -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; diff --git a/src/app/commands/cmd_layer_lock.cpp b/src/app/commands/cmd_layer_lock.cpp index 0911ed237..ba5e2fcfd 100644 --- a/src/app/commands/cmd_layer_lock.cpp +++ b/src/app/commands/cmd_layer_lock.cpp @@ -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(); } diff --git a/src/app/commands/cmd_layer_properties.cpp b/src/app/commands/cmd_layer_properties.cpp index d028fdc63..cd5a7c612 100644 --- a/src/app/commands/cmd_layer_properties.cpp +++ b/src/app/commands/cmd_layer_properties.cpp @@ -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; }; diff --git a/src/app/commands/cmd_layer_visibility.cpp b/src/app/commands/cmd_layer_visibility.cpp index 49d423934..55d5a2e9f 100644 --- a/src/app/commands/cmd_layer_visibility.cpp +++ b/src/app/commands/cmd_layer_visibility.cpp @@ -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(); } diff --git a/src/app/commands/cmd_new_frame.cpp b/src/app/commands/cmd_new_frame.cpp index 095b6de74..7a5a8ea81 100644 --- a/src/app/commands/cmd_new_frame.cpp +++ b/src/app/commands/cmd_new_frame.cpp @@ -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()) diff --git a/src/app/commands/cmd_new_frame_tag.cpp b/src/app/commands/cmd_new_frame_tag.cpp index 0b6794970..63df5c2f7 100644 --- a/src/app/commands/cmd_new_frame_tag.cpp +++ b/src/app/commands/cmd_new_frame_tag.cpp @@ -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 @@ -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() diff --git a/src/app/commands/cmd_reverse_frames.cpp b/src/app/commands/cmd_reverse_frames.cpp index 243cb00c2..a48317bc7 100644 --- a/src/app/commands/cmd_reverse_frames.cpp +++ b/src/app/commands/cmd_reverse_frames.cpp @@ -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 diff --git a/src/app/commands/cmd_select_palette.cpp b/src/app/commands/cmd_select_palette.cpp index 2f3a1e2a3..b94ea9160 100644 --- a/src/app/commands/cmd_select_palette.cpp +++ b/src/app/commands/cmd_select_palette.cpp @@ -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()); diff --git a/src/app/commands/cmd_set_loop_section.cpp b/src/app/commands/cmd_set_loop_section.cpp index 5baa325d8..e5c187a1d 100644 --- a/src/app/commands/cmd_set_loop_section.cpp +++ b/src/app/commands/cmd_set_loop_section.cpp @@ -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(); diff --git a/src/app/commands/cmd_undo.cpp b/src/app/commands/cmd_undo.cpp index c514e51a5..032799c1e 100644 --- a/src/app/commands/cmd_undo.cpp +++ b/src/app/commands/cmd_undo.cpp @@ -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(); diff --git a/src/app/context.cpp b/src/app/context.cpp index 75f38b458..419fc2a21 100644 --- a/src/app/context.cpp +++ b/src/app/context.cpp @@ -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); diff --git a/src/app/context.h b/src/app/context.h index 78eade8e9..5d7ffa886 100644 --- a/src/app/context.h +++ b/src/app/context.h @@ -22,6 +22,7 @@ #include "obs/observable.h" #include "obs/signal.h" #include "os/surface.h" +#include "view/range.h" #include #include @@ -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 m_preferences; std::unique_ptr m_draggedData = nullptr; + mutable view::RealRange m_range; // Last/current range // Result of the execution of a command. CommandResult m_result; diff --git a/src/app/site.cpp b/src/app/site.cpp index 3099f7d9c..d95eb460c 100644 --- a/src/app/site.cpp +++ b/src/app/site.cpp @@ -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; } } diff --git a/src/app/site.h b/src/app/site.h index 03aa496c6..a4ad38743 100644 --- a/src/app/site.h +++ b/src/app/site.h @@ -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; diff --git a/src/app/tx.h b/src/app/tx.h index 9b287d1c0..69e54171b 100644 --- a/src/app/tx.h +++ b/src/app/tx.h @@ -19,6 +19,10 @@ #include +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); } diff --git a/src/app/ui/editor/editor.cpp b/src/app/ui/editor/editor.cpp index ed60b852b..055e53e39 100644 --- a/src/app/ui/editor/editor.cpp +++ b/src/app/ui/editor/editor.cpp @@ -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()) { diff --git a/src/app/ui/editor/moving_cel_state.cpp b/src/app/ui/editor/moving_cel_state.cpp index d5f36acfc..241fba6d1 100644 --- a/src/app/ui/editor/moving_cel_state.cpp +++ b/src/app/ui/editor/moving_cel_state.cpp @@ -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); diff --git a/src/app/ui/editor/moving_slice_state.cpp b/src/app/ui/editor/moving_slice_state.cpp index a325ecc63..db27dfd11 100644 --- a/src/app/ui/editor/moving_slice_state.cpp +++ b/src/app/ui/editor/moving_slice_state.cpp @@ -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) { diff --git a/src/app/ui/editor/pixels_movement.cpp b/src/app/ui/editor/pixels_movement.cpp index e3b25dd35..9438f2524 100644 --- a/src/app/ui/editor/pixels_movement.cpp +++ b/src/app/ui/editor/pixels_movement.cpp @@ -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) diff --git a/src/app/ui/editor/standby_state.cpp b/src/app/ui/editor/standby_state.cpp index 6489b4e76..671821449 100644 --- a/src/app/ui/editor/standby_state.cpp +++ b/src/app/ui/editor/standby_state.cpp @@ -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(); diff --git a/src/app/ui/keyboard_shortcuts.cpp b/src/app/ui/keyboard_shortcuts.cpp index 2c04a851a..e9a416cd4 100644 --- a/src/app/ui/keyboard_shortcuts.cpp +++ b/src/app/ui/keyboard_shortcuts.cpp @@ -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; } diff --git a/src/app/ui/timeline/timeline.cpp b/src/app/ui/timeline/timeline.cpp index e1a669eac..2f2e37b0b 100644 --- a/src/app/ui/timeline/timeline.cpp +++ b/src/app/ui/timeline/timeline.cpp @@ -63,6 +63,7 @@ #include "ui/ui.h" #include "view/layers.h" #include "view/timeline_adapter.h" +#include "view/utils.h" #include #include @@ -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(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(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(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 tagsPerFrame(m_sprite->totalFrames(), 0); + std::vector tagsPerFrame(m_adapter->totalFrames(), 0); std::vector 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 (; bfromFrame() > 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(m_sprite); + invalidate(); +} + //static gfx::Color Timeline::highlightColor(const gfx::Color color) { diff --git a/src/app/ui/timeline/timeline.h b/src/app/ui/timeline/timeline.h index ca76a2fbb..322e9a4bc 100644 --- a/src/app/ui/timeline/timeline.h +++ b/src/app/ui/timeline/timeline.h @@ -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 @@ -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(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(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; diff --git a/src/app/ui_context.cpp b/src/app/ui_context.cpp index 888c02f93..1725f170b 100644 --- a/src/app/ui_context.cpp +++ b/src/app/ui_context.cpp @@ -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(); diff --git a/src/view/CMakeLists.txt b/src/view/CMakeLists.txt index 9a2c6a3cd..e7add3201 100644 --- a/src/view/CMakeLists.txt +++ b/src/view/CMakeLists.txt @@ -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 diff --git a/src/view/range.cpp b/src/view/range.cpp index ece8a562c..cc5e44979 100644 --- a/src/view/range.cpp +++ b/src/view/range.cpp @@ -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; } diff --git a/src/view/range.h b/src/view/range.h index 0d4002d27..73b5895b6 100644 --- a/src/view/range.h +++ b/src/view/range.h @@ -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; + // using RealRange = RangeT; + using VirtualRange = Range; + using RealRange = Range; + } // namespace view #endif // VIEW_RANGE_H_INCLUDED diff --git a/src/view/timeline_adapter.h b/src/view/timeline_adapter.h index 3ee4989ae..ef987c42d 100644 --- a/src/view/timeline_adapter.h +++ b/src/view/timeline_adapter.h @@ -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 diff --git a/src/view/utils.cpp b/src/view/utils.cpp new file mode 100644 index 000000000..481181f43 --- /dev/null +++ b/src/view/utils.cpp @@ -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 diff --git a/src/view/utils.h b/src/view/utils.h new file mode 100644 index 000000000..ef255331b --- /dev/null +++ b/src/view/utils.h @@ -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