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