diff --git a/laf b/laf index 0d8396ea4..c90b81aec 160000 --- a/laf +++ b/laf @@ -1 +1 @@ -Subproject commit 0d8396ea41cf4432f07b31d9002c80ef52e0565a +Subproject commit c90b81aec001293bdf7d19eb7feb22509716705e diff --git a/src/app/cli/app_options.cpp b/src/app/cli/app_options.cpp index 741863173..6e1a1981f 100644 --- a/src/app/cli/app_options.cpp +++ b/src/app/cli/app_options.cpp @@ -54,6 +54,7 @@ AppOptions::AppOptions(int argc, const char* argv[]) , m_allLayers(m_po.add("all-layers").description("Make all layers visible\nBy default hidden layers will be ignored")) , m_ignoreLayer(m_po.add("ignore-layer").requiresValue("").description("Exclude the given layer in the sheet\nor save as operation")) , m_tag(m_po.add("tag").alias("frame-tag").requiresValue("").description("Include tagged frames in the sheet")) + , m_playSubtags(m_po.add("play-subtags").description("Play subtags and repeats when saving the frames of an animated sprite")) , m_frameRange(m_po.add("frame-range").requiresValue("from,to").description("Only export frames in the [from,to] range")) , m_ignoreEmpty(m_po.add("ignore-empty").description("Do not export empty frames/cels")) , m_mergeDuplicates(m_po.add("merge-duplicates").description("Merge all duplicate frames into one in the sprite sheet")) diff --git a/src/app/cli/app_options.h b/src/app/cli/app_options.h index 5d3cbde43..eee082639 100644 --- a/src/app/cli/app_options.h +++ b/src/app/cli/app_options.h @@ -70,6 +70,7 @@ public: const Option& allLayers() const { return m_allLayers; } const Option& ignoreLayer() const { return m_ignoreLayer; } const Option& tag() const { return m_tag; } + const Option& playSubtags() const { return m_playSubtags; } const Option& frameRange() const { return m_frameRange; } const Option& ignoreEmpty() const { return m_ignoreEmpty; } const Option& mergeDuplicates() const { return m_mergeDuplicates; } @@ -143,6 +144,7 @@ private: Option& m_allLayers; Option& m_ignoreLayer; Option& m_tag; + Option& m_playSubtags; Option& m_frameRange; Option& m_ignoreEmpty; Option& m_mergeDuplicates; diff --git a/src/app/cli/cli_open_file.h b/src/app/cli/cli_open_file.h index 5e51f066b..003bcb030 100644 --- a/src/app/cli/cli_open_file.h +++ b/src/app/cli/cli_open_file.h @@ -44,6 +44,7 @@ namespace app { bool trimByGrid = false; bool oneFrame = false; bool exportTileset = false; + bool playSubtags = false; gfx::Rect crop; bool hasTag() const { diff --git a/src/app/cli/cli_processor.cpp b/src/app/cli/cli_processor.cpp index 9dbe65c1e..9ec346c66 100644 --- a/src/app/cli/cli_processor.cpp +++ b/src/app/cli/cli_processor.cpp @@ -303,6 +303,10 @@ int CliProcessor::process(Context* ctx) else if (opt == &m_options.tag()) { cof.tag = value.value(); } + // --play-subtags + else if (opt == &m_options.playSubtags()) { + cof.playSubtags = true; + } // --frame-range from,to else if (opt == &m_options.frameRange()) { std::vector splitRange; diff --git a/src/app/cli/default_cli_delegate.cpp b/src/app/cli/default_cli_delegate.cpp index 4f0e78241..0ab323827 100644 --- a/src/app/cli/default_cli_delegate.cpp +++ b/src/app/cli/default_cli_delegate.cpp @@ -88,6 +88,9 @@ void DefaultCliDelegate::saveFile(Context* ctx, const CliOpenFile& cof) if (cof.hasTag()) { params.set("frame-tag", cof.tag.c_str()); } + if (cof.playSubtags) { + params.set("playSubtags", "true"); + } if (cof.hasFrameRange()) { params.set("from-frame", base::convert_to(cof.fromFrame).c_str()); params.set("to-frame", base::convert_to(cof.toFrame).c_str()); diff --git a/src/app/cli/preview_cli_delegate.cpp b/src/app/cli/preview_cli_delegate.cpp index 82007e721..464675033 100644 --- a/src/app/cli/preview_cli_delegate.cpp +++ b/src/app/cli/preview_cli_delegate.cpp @@ -121,6 +121,10 @@ void PreviewCliDelegate::saveFile(Context* ctx, const CliOpenFile& cof) std::cout << " - Tag: '" << cof.tag << "'\n"; } + if (cof.playSubtags) { + std::cout << " - Play subtags & repeats\n"; + } + if (cof.hasSlice()) { std::cout << " - Slice: '" << cof.slice << "'\n"; } diff --git a/src/app/commands/cmd_close_file.cpp b/src/app/commands/cmd_close_file.cpp index 70d4bfe9a..61a89e2e5 100644 --- a/src/app/commands/cmd_close_file.cpp +++ b/src/app/commands/cmd_close_file.cpp @@ -1,4 +1,5 @@ // Aseprite +// Copyright (C) 2024 Igara Studio S.A. // Copyright (C) 2001-2018 David Capello // // This program is distributed under the terms of @@ -35,6 +36,8 @@ protected: bool onEnabled(Context* context) override { Workspace* workspace = App::instance()->workspace(); + if (!workspace) // Workspace (main window) can be null if we are in --batch mode + return false; WorkspaceView* view = workspace->activeView(); return (view != nullptr); } @@ -62,6 +65,8 @@ protected: void onExecute(Context* context) override { Workspace* workspace = App::instance()->workspace(); + if (!workspace) // Workspace (main window) can be null if we are in --batch mode + return; // Collect all document views DocViews docViews; diff --git a/src/app/commands/cmd_duplicate_view.cpp b/src/app/commands/cmd_duplicate_view.cpp index fbadd5971..adc99428e 100644 --- a/src/app/commands/cmd_duplicate_view.cpp +++ b/src/app/commands/cmd_duplicate_view.cpp @@ -1,4 +1,5 @@ // Aseprite +// Copyright (C) 2024 Igara Studio S.A. // Copyright (C) 2001-2017 David Capello // // This program is distributed under the terms of @@ -35,6 +36,8 @@ DuplicateViewCommand::DuplicateViewCommand() bool DuplicateViewCommand::onEnabled(Context* context) { Workspace* workspace = App::instance()->workspace(); + if (!workspace) // Workspace (main window) can be null if we are in --batch mode + return false; WorkspaceView* view = workspace->activeView(); return (view != nullptr); } diff --git a/src/app/commands/cmd_goto_tab.cpp b/src/app/commands/cmd_goto_tab.cpp index 41fce37c5..ad548ff35 100644 --- a/src/app/commands/cmd_goto_tab.cpp +++ b/src/app/commands/cmd_goto_tab.cpp @@ -1,4 +1,5 @@ // Aseprite +// Copyright (C) 2024 Igara Studio S.A. // Copyright (C) 2001-2017 David Capello // // This program is distributed under the terms of @@ -30,7 +31,10 @@ GotoNextTabCommand::GotoNextTabCommand() bool GotoNextTabCommand::onEnabled(Context* context) { - return App::instance()->workspace()->canSelectOtherTab(); + Workspace* workspace = App::instance()->workspace(); + if (!workspace) // Workspace (main window) can be null if we are in --batch mode + return false; + return workspace->canSelectOtherTab(); } void GotoNextTabCommand::onExecute(Context* context) @@ -54,7 +58,10 @@ GotoPreviousTabCommand::GotoPreviousTabCommand() bool GotoPreviousTabCommand::onEnabled(Context* context) { - return App::instance()->workspace()->canSelectOtherTab(); + Workspace* workspace = App::instance()->workspace(); + if (!workspace) // Workspace (main window) can be null if we are in --batch mode + return false; + return workspace->canSelectOtherTab(); } void GotoPreviousTabCommand::onExecute(Context* context) diff --git a/src/app/commands/cmd_save_file.cpp b/src/app/commands/cmd_save_file.cpp index c5c6923a0..4f00aba55 100644 --- a/src/app/commands/cmd_save_file.cpp +++ b/src/app/commands/cmd_save_file.cpp @@ -494,29 +494,28 @@ void SaveFileCopyAsCommand::onExecute(Context* context) { RestoreVisibleLayers layersVisibility; + Site site = context->activeSite(); if (context->isUIAvailable()) { - Site site = context->activeSite(); - // Selected layers to export calculate_visible_layers(site, layers, layersIndex, layersVisibility); - - // m_selFrames is not empty if fromFrame/toFrame parameters are - // specified. - if (m_framesSeq.empty()) { - // Frames sequence to export - FramesSequence framesSeq; - Tag* tag = calculate_frames_sequence( - site, frames, framesSeq, isPlaySubtags, aniDirValue); - if (tag) - params().tag(tag->name()); - m_framesSeq = framesSeq; - } - m_adjustFramesByTag = false; } + // m_selFrames is not empty if fromFrame/toFrame parameters are + // specified. + if (m_framesSeq.empty()) { + // Frames sequence to export + FramesSequence framesSeq; + Tag* tag = calculate_frames_sequence( + site, frames, framesSeq, isPlaySubtags, aniDirValue); + if (tag) + params().tag(tag->name()); + m_framesSeq = framesSeq; + } + m_adjustFramesByTag = false; + // Set other parameters params().aniDir(aniDirValue); if (!bounds.isEmpty()) diff --git a/src/app/ui/timeline/timeline.cpp b/src/app/ui/timeline/timeline.cpp index 8eb5fb5c7..d2cfa2f83 100644 --- a/src/app/ui/timeline/timeline.cpp +++ b/src/app/ui/timeline/timeline.cpp @@ -27,6 +27,7 @@ #include "app/doc_range_ops.h" #include "app/doc_undo.h" #include "app/i18n/strings.h" +#include "app/inline_command_execution.h" #include "app/loop_tag.h" #include "app/modules/gfx.h" #include "app/modules/gui.h" @@ -1390,6 +1391,7 @@ bool Timeline::onProcessMessage(Message* msg) if ((m_state == STATE_RESIZING_TAG_LEFT && tag->fromFrame() != m_resizeTagData.from) || (m_state == STATE_RESIZING_TAG_RIGHT && tag->toFrame() != m_resizeTagData.to)) { try { + InlineCommandExecution inlineCmd(m_context); ContextWriter writer(m_context); Tx tx(writer, Strings::commands_FrameTagProperties()); tx(new cmd::SetTagRange( diff --git a/tests/cli/save-as.sh b/tests/cli/save-as.sh index f5803b884..998efcac6 100644 --- a/tests/cli/save-as.sh +++ b/tests/cli/save-as.sh @@ -407,3 +407,28 @@ assert(d3.bounds == Rectangle(0, 0, 6, 4)) assert(d4.bounds == Rectangle(0, 0, 8, 4)) EOF $ASEPRITE -b -script "$d/compare.lua" || exit 1 + +# --play-subtags --save-as +d=$t/save-as-play-subtags +$ASEPRITE -b sprites/tags3x123reps.aseprite --play-subtags --save-as $d/image{frame01}.png || exit 1 +expect "image01.png +image02.png +image03.png +image04.png +image05.png +image06.png +image07.png +image08.png +image09.png +image10.png +image11.png" "list_files $d" +cat >$d/compare.lua <