diff --git a/src/app/commands/cmd_flip.cpp b/src/app/commands/cmd_flip.cpp index 9024de22a..042e5c892 100644 --- a/src/app/commands/cmd_flip.cpp +++ b/src/app/commands/cmd_flip.cpp @@ -96,8 +96,8 @@ void FlipCommand::onExecute(Context* ctx) cels = get_unlocked_unique_cels(site.sprite(), range); } else if (site.cel() && - site.layer() && - site.layer()->isEditable()) { + site.layer() && + site.layer()->canEditPixels()) { cels.push_back(site.cel()); } diff --git a/src/app/commands/cmd_rotate.cpp b/src/app/commands/cmd_rotate.cpp index f879da4e4..1b5360a79 100644 --- a/src/app/commands/cmd_rotate.cpp +++ b/src/app/commands/cmd_rotate.cpp @@ -1,5 +1,5 @@ // Aseprite -// Copyright (C) 2019-2020 Igara Studio S.A. +// Copyright (C) 2019-2021 Igara Studio S.A. // Copyright (C) 2001-2018 David Capello // // This program is distributed under the terms of @@ -217,7 +217,7 @@ void RotateCommand::onExecute(Context* context) cels = get_unlocked_unique_cels(site.sprite(), range); else if (site.cel() && site.layer() && - site.layer()->isEditable()) { + site.layer()->canEditPixels()) { cels.push_back(site.cel()); } diff --git a/src/app/commands/filters/filter_manager_impl.cpp b/src/app/commands/filters/filter_manager_impl.cpp index b9faeba8d..2ba36c495 100644 --- a/src/app/commands/filters/filter_manager_impl.cpp +++ b/src/app/commands/filters/filter_manager_impl.cpp @@ -1,5 +1,5 @@ // Aseprite -// Copyright (C) 2019-2020 Igara Studio S.A. +// Copyright (C) 2019-2021 Igara Studio S.A. // Copyright (C) 2001-2018 David Capello // // This program is distributed under the terms of @@ -281,8 +281,7 @@ void FilterManagerImpl::applyToTarget() } else if (m_site.cel() && m_site.layer() && - m_site.layer()->isEditable() && - !m_site.layer()->isReference()) { + m_site.layer()->canEditPixels()) { cels.push_back(m_site.cel()); } break; @@ -290,8 +289,7 @@ void FilterManagerImpl::applyToTarget() case CelsTarget::All: { for (Cel* cel : m_site.sprite()->uniqueCels()) { - if (cel->layer()->isEditable() && - !cel->layer()->isReference()) + if (cel->layer()->canEditPixels()) cels.push_back(cel); } break; diff --git a/src/app/ini_file.cpp b/src/app/ini_file.cpp index ed78e8dfd..c6022b57e 100644 --- a/src/app/ini_file.cpp +++ b/src/app/ini_file.cpp @@ -1,5 +1,5 @@ // Aseprite -// Copyright (C) 2018-2019 Igara Studio S.A. +// Copyright (C) 2018-2021 Igara Studio S.A. // Copyright (C) 2001-2016 David Capello // // This program is distributed under the terms of @@ -81,7 +81,7 @@ ConfigModule::ConfigModule() #elif !defined(_WIN32) // On Linux we migrate the old configuration file name - // (.asepriterc -> ~/.config/aseprite/aseprite.ini) + // (~/.asepriterc -> ~/.config/aseprite/aseprite.ini) { ResourceFinder old_rf; old_rf.includeHomeDir(".asepriterc"); diff --git a/src/app/pref/preferences.cpp b/src/app/pref/preferences.cpp index 830e8838b..a40934080 100644 --- a/src/app/pref/preferences.cpp +++ b/src/app/pref/preferences.cpp @@ -1,5 +1,5 @@ // Aseprite -// Copyright (C) 2018-2020 Igara Studio S.A. +// Copyright (C) 2018-2021 Igara Studio S.A. // Copyright (C) 2001-2018 David Capello // // This program is distributed under the terms of @@ -191,6 +191,10 @@ DocumentPreferences& Preferences::document(const Doc* doc) void Preferences::resetToolPreferences(tools::Tool* tool) { + if (tool->prefAlreadyResetFromScript()) + return; + tool->markPrefAlreadyResetFromScript(); + auto it = m_tools.find(tool->getId()); if (it != m_tools.end()) m_tools.erase(it); diff --git a/src/app/resource_finder.cpp b/src/app/resource_finder.cpp index 3b6f7bf65..ef056bf5d 100644 --- a/src/app/resource_finder.cpp +++ b/src/app/resource_finder.cpp @@ -1,5 +1,5 @@ // Aseprite -// Copyright (C) 2019-2020 Igara Studio S.A. +// Copyright (C) 2019-2021 Igara Studio S.A. // Copyright (C) 2001-2016 David Capello // // This program is distributed under the terms of @@ -105,8 +105,8 @@ void ResourceFinder::includeDataDir(const char* filename) #else // $HOME/.config/aseprite/filename - sprintf(buf, ".config/aseprite/data/%s", filename); - includeHomeDir(buf); + sprintf(buf, "aseprite/data/%s", filename); + includeHomeConfigDir(buf); // $BINDIR/data/filename sprintf(buf, "data/%s", filename); @@ -150,6 +150,24 @@ void ResourceFinder::includeHomeDir(const char* filename) #endif } +#if !defined(_WIN32) && !defined(__APPLE__) + +// For Linux: It's $XDG_CONFIG_HOME or $HOME/.config +void ResourceFinder::includeHomeConfigDir(const char* filename) +{ + char* configHome = std::getenv("XDG_CONFIG_HOME"); + if (configHome && *configHome) { + // $XDG_CONFIG_HOME/filename + addPath(base::join_path(configHome, filename)); + } + else { + // $HOME/.config/filename + includeHomeDir(base::join_path(std::string(".config"), filename).c_str()); + } +} + +#endif // !defined(_WIN32) && !defined(__APPLE__) + void ResourceFinder::includeUserDir(const char* filename) { #ifdef _WIN32 @@ -185,7 +203,7 @@ void ResourceFinder::includeUserDir(const char* filename) #else // !__APPLE__ // $HOME/.config/aseprite/filename - includeHomeDir((std::string(".config/aseprite/") + filename).c_str()); + includeHomeConfigDir((std::string("aseprite/") + filename).c_str()); #endif } diff --git a/src/app/resource_finder.h b/src/app/resource_finder.h index 5d1099a11..f5bdc35f9 100644 --- a/src/app/resource_finder.h +++ b/src/app/resource_finder.h @@ -1,5 +1,5 @@ // Aseprite -// Copyright (C) 2019-2020 Igara Studio S.A. +// Copyright (C) 2019-2021 Igara Studio S.A. // Copyright (C) 2001-2018 David Capello // // This program is distributed under the terms of @@ -42,6 +42,11 @@ namespace app { void includeDataDir(const char* filename); void includeHomeDir(const char* filename); +#if !defined(_WIN32) && !defined(__APPLE__) + // For Linux: It's $XDG_CONFIG_HOME or $HOME/.config + void includeHomeConfigDir(const char* filename); +#endif + // Tries to add the given filename in these locations: // For Windows: // - If ASEPRITE_USER_FOLDER environment variable is defined, it diff --git a/src/app/tools/tool.h b/src/app/tools/tool.h index 7418898cf..1819c6036 100644 --- a/src/app/tools/tool.h +++ b/src/app/tools/tool.h @@ -1,4 +1,5 @@ // Aseprite +// Copyright (C) 2021 Igara Studio S.A. // Copyright (C) 2001-2018 David Capello // // This program is distributed under the terms of @@ -61,6 +62,9 @@ namespace app { void setIntertwine(int button, Intertwine* intertwine) { m_button[button].m_intertwine = intertwine; } void setTracePolicy(int button, TracePolicy trace_policy) { m_button[button].m_trace_policy = trace_policy; } + bool prefAlreadyResetFromScript() const { return m_prefAlreadyResetFromScript; } + void markPrefAlreadyResetFromScript() { m_prefAlreadyResetFromScript = true; } + private: ToolGroup* m_group; std::string m_id; @@ -68,6 +72,13 @@ namespace app { std::string m_tips; int m_default_brush_size; + // Flag used to indicate that the preferences of this tool were + // already reset from scripts when they are executed in CLI mode + // (without GUI). This is needed to reset the preferences only + // once, but if the script then modifies the preferences, they + // are not reset again. + bool m_prefAlreadyResetFromScript = false; + struct { Fill m_fill; Ink* m_ink; diff --git a/src/app/ui/editor/editor.cpp b/src/app/ui/editor/editor.cpp index 9cf9a4fd4..4161461be 100644 --- a/src/app/ui/editor/editor.cpp +++ b/src/app/ui/editor/editor.cpp @@ -409,6 +409,7 @@ void Editor::getSite(Site* site) const // TODO we should not access timeline directly here Timeline* timeline = App::instance()->timeline(); if (timeline && + timeline->isVisible() && timeline->range().enabled()) { site->range(timeline->range()); } diff --git a/src/app/ui/editor/moving_cel_state.cpp b/src/app/ui/editor/moving_cel_state.cpp index eed469b46..dd6256acd 100644 --- a/src/app/ui/editor/moving_cel_state.cpp +++ b/src/app/ui/editor/moving_cel_state.cpp @@ -47,8 +47,10 @@ MovingCelCollect::MovingCelCollect(Editor* editor, Layer* layer) if (layer && layer->isImage()) m_mainCel = layer->cel(editor->frame()); - DocRange range = App::instance()->timeline()->range(); - if (!range.enabled()) { + Timeline* timeline = App::instance()->timeline(); + DocRange range = timeline->range(); + if (!range.enabled() || + !timeline->isVisible()) { range.startRange(editor->layer(), editor->frame(), DocRange::kCels); range.endRange(editor->layer(), editor->frame()); } @@ -86,8 +88,6 @@ MovingCelState::MovingCelState(Editor* editor, , m_celList(collect.celList()) , m_celOffset(0.0, 0.0) , m_celScale(1.0, 1.0) - , m_hasReference(false) - , m_scaled(false) , m_handle(handle) , m_editor(editor) { @@ -188,6 +188,13 @@ bool MovingCelState::onMouseUp(Editor* editor, MouseMessage* msg) // like to update all the editors. document->notifyGeneralUpdate(); } + // Just a click in the current layer + else if (!m_moved & !m_scaled) { + // Deselect the whole range if we are in "Auto Select Layer" + if (editor->isAutoSelectLayer()) { + App::instance()->timeline()->clearAndInvalidateRange(); + } + } // Restore the mask visibility. if (m_maskVisible) { @@ -218,6 +225,8 @@ bool MovingCelState::onMouseMove(Editor* editor, MouseMessage* msg) m_celOffset.y = 0; } } + if (!m_moved && intCelOffset() != gfx::Point(0, 0)) + m_moved = true; break; case ScaleSEHandle: { @@ -246,6 +255,7 @@ bool MovingCelState::onMouseMove(Editor* editor, MouseMessage* msg) if (cel->layer()->isReference()) { celBounds.x += m_celOffset.x; celBounds.y += m_celOffset.y; + m_moved = true; if (m_scaled) { celBounds.w *= m_celScale.w; celBounds.h *= m_celScale.h; diff --git a/src/app/ui/editor/moving_cel_state.h b/src/app/ui/editor/moving_cel_state.h index 509bfba8d..aa4a82a87 100644 --- a/src/app/ui/editor/moving_cel_state.h +++ b/src/app/ui/editor/moving_cel_state.h @@ -68,8 +68,9 @@ namespace app { gfx::SizeF m_celMainSize; gfx::SizeF m_celScale; bool m_maskVisible; - bool m_hasReference; - bool m_scaled; + bool m_hasReference = false; + bool m_moved = false; + bool m_scaled = false; HandleType m_handle; Editor* m_editor; diff --git a/src/app/ui/editor/moving_pixels_state.cpp b/src/app/ui/editor/moving_pixels_state.cpp index 6ac723245..045118568 100644 --- a/src/app/ui/editor/moving_pixels_state.cpp +++ b/src/app/ui/editor/moving_pixels_state.cpp @@ -1,5 +1,5 @@ // Aseprite -// Copyright (C) 2019-2020 Igara Studio S.A. +// Copyright (C) 2019-2021 Igara Studio S.A. // Copyright (C) 2001-2018 David Capello // // This program is distributed under the terms of @@ -36,6 +36,7 @@ #include "app/ui/keyboard_shortcuts.h" #include "app/ui/main_window.h" #include "app/ui/status_bar.h" +#include "app/ui/timeline/timeline.h" #include "app/ui_context.h" #include "app/util/clipboard.h" #include "app/util/layer_utils.h" @@ -114,10 +115,14 @@ MovingPixelsState::MovingPixelsState(Editor* editor, MouseMessage* msg, PixelsMo ContextBar* contextBar = App::instance()->contextBar(); contextBar->updateForMovingPixels(getTransformation(editor)); contextBar->add_observer(this); + + App::instance()->mainWindow()->getTimeline()->add_observer(this); } MovingPixelsState::~MovingPixelsState() { + App::instance()->mainWindow()->getTimeline()->remove_observer(this); + ContextBar* contextBar = App::instance()->contextBar(); contextBar->remove_observer(this); contextBar->updateForActiveTool(); @@ -723,6 +728,15 @@ void MovingPixelsState::onBeforeLayerChanged(Editor* editor) dropPixels(); } +void MovingPixelsState::onBeforeRangeChanged(Timeline* timeline) +{ + if (!isActiveDocument()) + return; + + if (m_pixelsMovement) + dropPixels(); +} + void MovingPixelsState::onTransparentColorChange() { ASSERT(m_pixelsMovement); diff --git a/src/app/ui/editor/moving_pixels_state.h b/src/app/ui/editor/moving_pixels_state.h index 3046772af..99e3292ee 100644 --- a/src/app/ui/editor/moving_pixels_state.h +++ b/src/app/ui/editor/moving_pixels_state.h @@ -1,5 +1,5 @@ // Aseprite -// Copyright (C) 2019-2020 Igara Studio S.A. +// Copyright (C) 2019-2021 Igara Studio S.A. // Copyright (C) 2001-2017 David Capello // // This program is distributed under the terms of @@ -15,6 +15,7 @@ #include "app/ui/editor/pixels_movement.h" #include "app/ui/editor/standby_state.h" #include "app/ui/status_bar.h" +#include "app/ui/timeline/timeline_observer.h" #include "obs/connection.h" #include "ui/timer.h" @@ -29,6 +30,7 @@ namespace app { class MovingPixelsState : public StandbyState , EditorObserver + , TimelineObserver , ContextBarObserver , PixelsMovementDelegate { public: @@ -66,6 +68,9 @@ namespace app { virtual void onBeforeFrameChanged(Editor* editor) override; virtual void onBeforeLayerChanged(Editor* editor) override; + // TimelineObserver + virtual void onBeforeRangeChanged(Timeline* timeline) override; + // ContextBarObserver virtual void onDropPixels(ContextBarObserver::DropAction action) override; diff --git a/src/app/ui/editor/pixels_movement.cpp b/src/app/ui/editor/pixels_movement.cpp index 237906272..afc25090d 100644 --- a/src/app/ui/editor/pixels_movement.cpp +++ b/src/app/ui/editor/pixels_movement.cpp @@ -1293,16 +1293,32 @@ CelList PixelsMovement::getEditableCels() // TODO This case is used in paste too, where the cel() can be // nullptr (e.g. we paste the clipboard image into an empty // cel). - if (m_site.layer() && m_site.layer()->isEditableHierarchy()) + if (m_site.layer() && + m_site.layer()->canEditPixels()) { cels.push_back(m_site.cel()); + } return cels; } // Current cel (m_site.cel()) can be nullptr when we paste in an // empty cel (Ctrl+V) and cut (Ctrl+X) the floating pixels. if (m_site.cel() && - m_site.cel()->layer()->isEditableHierarchy()) { - auto it = std::find(cels.begin(), cels.end(), m_site.cel()); + m_site.cel()->layer()->canEditPixels()) { + CelList::iterator it; + + // If we are in a linked cel, remove the cel that matches the + // linked cel. In this way we avoid having two Cel in cels + // pointing to the same CelData. + if (Cel* link = m_site.cel()->link()) { + it = std::find_if(cels.begin(), cels.end(), + [link](const Cel* cel){ + return (cel == link || + cel->link() == link); + }); + } + else { + it = std::find(cels.begin(), cels.end(), m_site.cel()); + } if (it != cels.end()) cels.erase(it); cels.insert(cels.begin(), m_site.cel()); diff --git a/src/app/ui/timeline/timeline.cpp b/src/app/ui/timeline/timeline.cpp index 3cb06bda1..1d01d66a6 100644 --- a/src/app/ui/timeline/timeline.cpp +++ b/src/app/ui/timeline/timeline.cpp @@ -4051,6 +4051,8 @@ void Timeline::invalidateRange() void Timeline::clearAndInvalidateRange() { if (m_range.enabled()) { + notify_observers(&TimelineObserver::onBeforeRangeChanged, this); + invalidateRange(); m_range.clearRange(); } diff --git a/src/app/ui/timeline/timeline.h b/src/app/ui/timeline/timeline.h index 222d64c15..fabe5c546 100644 --- a/src/app/ui/timeline/timeline.h +++ b/src/app/ui/timeline/timeline.h @@ -1,5 +1,5 @@ // Aseprite -// Copyright (C) 2018-2020 Igara Studio S.A. +// Copyright (C) 2018-2021 Igara Studio S.A. // Copyright (C) 2001-2018 David Capello // // This program is distributed under the terms of @@ -17,6 +17,7 @@ #include "app/ui/editor/editor_observer.h" #include "app/ui/input_chain_element.h" #include "app/ui/timeline/ani_controls.h" +#include "app/ui/timeline/timeline_observer.h" #include "base/debug.h" #include "doc/frame.h" #include "doc/layer.h" @@ -26,6 +27,7 @@ #include "doc/tag.h" #include "gfx/color.h" #include "obs/connection.h" +#include "obs/observable.h" #include "ui/scroll_bar.h" #include "ui/timer.h" #include "ui/widget.h" @@ -60,6 +62,7 @@ namespace app { class Timeline : public ui::Widget, public ui::ScrollableViewDelegate, + public obs::observable, public ContextObserver, public DocsObserver, public DocObserver, diff --git a/src/app/ui/timeline/timeline_observer.h b/src/app/ui/timeline/timeline_observer.h new file mode 100644 index 000000000..df011a2c1 --- /dev/null +++ b/src/app/ui/timeline/timeline_observer.h @@ -0,0 +1,24 @@ +// Aseprite +// Copyright (C) 2021 Igara Studio S.A. +// +// This program is distributed under the terms of +// the End-User License Agreement for Aseprite. + +#ifndef APP_UI_TIMELINE_TIMELINE_OBSERVER_H_INCLUDED +#define APP_UI_TIMELINE_TIMELINE_OBSERVER_H_INCLUDED +#pragma once + +namespace app { + class Timeline; + + class TimelineObserver { + public: + virtual ~TimelineObserver() { } + + // Called when the current timeline range is going to change. + virtual void onBeforeRangeChanged(Timeline* timeline) { } + }; + +} // namespace app + +#endif diff --git a/src/app/ui_context.cpp b/src/app/ui_context.cpp index c1a028d10..ca6de4892 100644 --- a/src/app/ui_context.cpp +++ b/src/app/ui_context.cpp @@ -338,9 +338,14 @@ void UIContext::onGetActiveSite(Site* site) const view->getSite(site); if (site->sprite()) { - // Selected range in the timeline + // Selected range in the timeline. We use it only if the + // timeline is visible. A common scenario might be + // undoing/redoing actions where the range is re-selected, that + // could enable the range even if the timeline is hidden. In + // this way we avoid using the timeline selection unexpectedly. Timeline* timeline = App::instance()->timeline(); if (timeline && + timeline->isVisible() && timeline->range().enabled()) { site->range(timeline->range()); } diff --git a/src/app/util/range_utils.cpp b/src/app/util/range_utils.cpp index 693601883..ce5ac62a8 100644 --- a/src/app/util/range_utils.cpp +++ b/src/app/util/range_utils.cpp @@ -1,5 +1,5 @@ // Aseprite -// Copyright (C) 2020 Igara Studio S.A. +// Copyright (C) 2020-2021 Igara Studio S.A. // Copyright (C) 2001-2018 David Capello // // This program is distributed under the terms of @@ -25,6 +25,7 @@ namespace app { using namespace doc; // TODO the DocRange should be "iteratable" to replace this function +// or we can wait to C++20 coroutines and co_yield static CelList get_cels_templ(const Sprite* sprite, DocRange range, const bool onlyUniqueCels, @@ -39,8 +40,9 @@ static CelList get_cels_templ(const Sprite* sprite, for (Layer* layer : range.selectedLayers()) { if (!layer || !layer->isImage() || - (onlyUnlockedCel && !layer->isEditableHierarchy())) + (onlyUnlockedCel && !layer->canEditPixels())) { continue; + } LayerImage* layerImage = static_cast(layer); for (frame_t frame : range.selectedFrames()) { diff --git a/src/doc/LICENSE.txt b/src/doc/LICENSE.txt index da9a44fa3..3b086de25 100644 --- a/src/doc/LICENSE.txt +++ b/src/doc/LICENSE.txt @@ -1,4 +1,4 @@ -Copyright (c) 2018-2020 Igara Studio S.A. +Copyright (c) 2018-2021 Igara Studio S.A. Copyright (c) 2001-2018 David Capello Permission is hereby granted, free of charge, to any person obtaining diff --git a/src/doc/layer.cpp b/src/doc/layer.cpp index a23497dc8..8bbf61014 100644 --- a/src/doc/layer.cpp +++ b/src/doc/layer.cpp @@ -1,5 +1,5 @@ // Aseprite Document Library -// Copyright (C) 2019-2020 Igara Studio S.A. +// Copyright (C) 2019-2021 Igara Studio S.A. // Copyright (C) 2001-2018 David Capello // // This file is released under the terms of the MIT license. @@ -180,6 +180,24 @@ bool Layer::isEditableHierarchy() const return true; } +// It's like isVisibleHierarchy + isEditableHierarchy. Returns true if +// the whole layer hierarchy is unlocked and visible, so the user can +// edit its pixels without unexpected side-effects (e.g. editing +// hidden layers). +bool Layer::canEditPixels() const +{ + const Layer* layer = this; + while (layer) { + if (!layer->isVisible() || + !layer->isEditable() || + layer->isReference()) { // Cannot edit pixels from reference layers + return false; + } + layer = layer->parent(); + } + return true; +} + bool Layer::hasAncestor(const Layer* ancestor) const { Layer* it = parent(); diff --git a/src/doc/layer.h b/src/doc/layer.h index c44672e6f..b8e153f3d 100644 --- a/src/doc/layer.h +++ b/src/doc/layer.h @@ -1,5 +1,5 @@ // Aseprite Document Library -// Copyright (C) 2019-2020 Igara Studio S.A. +// Copyright (C) 2019-2021 Igara Studio S.A. // Copyright (C) 2001-2018 David Capello // // This file is released under the terms of the MIT license. @@ -92,6 +92,7 @@ namespace doc { bool isVisibleHierarchy() const; bool isEditableHierarchy() const; + bool canEditPixels() const; bool hasAncestor(const Layer* ancestor) const; void setBackground(bool state) { switchFlags(LayerFlags::Background, state); } diff --git a/src/flic b/src/flic index 1e0630d31..876ef60df 160000 --- a/src/flic +++ b/src/flic @@ -1 +1 @@ -Subproject commit 1e0630d310b55abf7d16d3d89feeb13936d540f8 +Subproject commit 876ef60df5fec606f8eb0638ee893e4967db4673 diff --git a/src/tga b/src/tga index ea8005303..16b8bb25e 160000 --- a/src/tga +++ b/src/tga @@ -1 +1 @@ -Subproject commit ea8005303f42925fa1180d1f6e973a816c0b80af +Subproject commit 16b8bb25e34bfb0193609012257502be62539d35