mirror of
https://github.com/aseprite/aseprite.git
synced 2025-02-04 06:40:07 +00:00
Allow to save specific layers/frames in Save Copy As (fix #1080)
This commit is contained in:
parent
1404b0ce7d
commit
3e9e49caf9
@ -284,6 +284,8 @@
|
||||
<section id="save_copy">
|
||||
<option id="filename" type="std::string" />
|
||||
<option id="resize_scale" type="double" default="1" />
|
||||
<option id="layer" type="std::string" />
|
||||
<option id="frame_tag" type="std::string" />
|
||||
</section>
|
||||
<section id="sprite_sheet">
|
||||
<option id="defined" type="bool" default="false" />
|
||||
|
@ -20,23 +20,11 @@
|
||||
<label text="File type:" />
|
||||
<hbox cell_align="horizontal">
|
||||
<combobox id="file_type" minwidth="70" />
|
||||
<hbox id="resize_options">
|
||||
<label text="Resize:" />
|
||||
<combobox id="resize">
|
||||
<listitem text="25%" value="0.25" />
|
||||
<listitem text="50%" value="0.5" />
|
||||
<listitem text="100%" value="1" />
|
||||
<listitem text="200%" value="2" />
|
||||
<listitem text="300%" value="3" />
|
||||
<listitem text="400%" value="4" />
|
||||
<listitem text="500%" value="5" />
|
||||
<listitem text="600%" value="6" />
|
||||
<listitem text="700%" value="7" />
|
||||
<listitem text="800%" value="8" />
|
||||
<listitem text="900%" value="9" />
|
||||
<listitem text="1000%" value="10" />
|
||||
</combobox>
|
||||
</hbox>
|
||||
<vbox>
|
||||
<boxfiller />
|
||||
<link id="extra_options" text="" />
|
||||
<boxfiller />
|
||||
</vbox>
|
||||
<boxfiller />
|
||||
<box horizontal="true" homogeneous="true">
|
||||
<button text="&OK" closewindow="true" id="ok" magnet="true" width="60" />
|
||||
|
29
data/widgets/file_selector_extras.xml
Normal file
29
data/widgets/file_selector_extras.xml
Normal file
@ -0,0 +1,29 @@
|
||||
<!-- ASEPRITE -->
|
||||
<!-- Copyright (C) 2016 by David Capello -->
|
||||
<gui>
|
||||
<vbox id="file_selector_extras">
|
||||
<grid columns="2">
|
||||
<label id="resize_label" text="Resize:" />
|
||||
<combobox id="resize" cell_align="horizontal">
|
||||
<listitem text="25%" value="0.25" />
|
||||
<listitem text="50%" value="0.5" />
|
||||
<listitem text="100%" value="1" />
|
||||
<listitem text="200%" value="2" />
|
||||
<listitem text="300%" value="3" />
|
||||
<listitem text="400%" value="4" />
|
||||
<listitem text="500%" value="5" />
|
||||
<listitem text="600%" value="6" />
|
||||
<listitem text="700%" value="7" />
|
||||
<listitem text="800%" value="8" />
|
||||
<listitem text="900%" value="9" />
|
||||
<listitem text="1000%" value="10" />
|
||||
</combobox>
|
||||
|
||||
<label id="layers_label" text="Layers:" />
|
||||
<combobox id="layers" text="" cell_align="horizontal" />
|
||||
|
||||
<label id="frames_label" text="Frames:" />
|
||||
<combobox id="frames" text="" cell_align="horizontal" />
|
||||
</grid>
|
||||
</vbox>
|
||||
</gui>
|
@ -400,6 +400,7 @@ add_library(app-lib
|
||||
ui/icon_button.cpp
|
||||
ui/input_chain.cpp
|
||||
ui/keyboard_shortcuts.cpp
|
||||
ui/layer_frame_comboboxes.cpp
|
||||
ui/main_menu_bar.cpp
|
||||
ui/main_window.cpp
|
||||
ui/news_listbox.cpp
|
||||
|
@ -36,7 +36,10 @@ CliOpenFile::CliOpenFile()
|
||||
FileOpROI CliOpenFile::roi() const
|
||||
{
|
||||
ASSERT(document);
|
||||
return FileOpROI(document, frameTag, fromFrame, toFrame);
|
||||
SelectedFrames selFrames;
|
||||
if (hasFrameRange())
|
||||
selFrames.insert(fromFrame, toFrame);
|
||||
return FileOpROI(document, frameTag, selFrames, true);
|
||||
}
|
||||
|
||||
} // namespace app
|
||||
|
@ -113,10 +113,19 @@ void PreviewCliDelegate::saveFile(const CliOpenFile& cof)
|
||||
}
|
||||
|
||||
if (cof.hasFrameRange()) {
|
||||
auto roi = cof.roi();
|
||||
const auto& selFrames = cof.roi().selectedFrames();
|
||||
if (!selFrames.empty()) {
|
||||
if (selFrames.ranges() == 1)
|
||||
std::cout << " - Frame range from "
|
||||
<< roi.fromFrame() << " to "
|
||||
<< roi.toFrame() << "\n";
|
||||
<< selFrames.firstFrame() << " to "
|
||||
<< selFrames.lastFrame() << "\n";
|
||||
else {
|
||||
std::cout << " - Specific frames:";
|
||||
for (auto frame : selFrames)
|
||||
std::cout << ' ' << frame;
|
||||
std::cout << "\n";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!cof.filenameFormat.empty())
|
||||
|
@ -20,6 +20,7 @@
|
||||
#include "app/pref/preferences.h"
|
||||
#include "app/restore_visible_layers.h"
|
||||
#include "app/ui/editor/editor.h"
|
||||
#include "app/ui/layer_frame_comboboxes.h"
|
||||
#include "app/ui/status_bar.h"
|
||||
#include "app/ui/timeline.h"
|
||||
#include "base/bind.h"
|
||||
@ -41,11 +42,6 @@ using namespace ui;
|
||||
|
||||
namespace {
|
||||
|
||||
static const char* kAllLayers = "";
|
||||
static const char* kAllFrames = "";
|
||||
static const char* kSelectedLayers = "**selected-layers**";
|
||||
static const char* kSelectedFrames = "**selected-frames**";
|
||||
|
||||
// Special key value used in default preferences to know if by default
|
||||
// the user wants to generate texture and/or files.
|
||||
static const char* kSpecifiedFilename = "**filename**";
|
||||
@ -158,78 +154,13 @@ namespace {
|
||||
return true;
|
||||
}
|
||||
|
||||
FrameTag* calculate_selected_frames(const Sprite* sprite,
|
||||
const std::string& frameTagName,
|
||||
SelectedFrames& selFrames) {
|
||||
FrameTag* frameTag = nullptr;
|
||||
|
||||
if (frameTagName == kSelectedFrames) {
|
||||
auto range = App::instance()->timeline()->range();
|
||||
if (range.enabled()) {
|
||||
selFrames = range.selectedFrames();
|
||||
}
|
||||
else if (current_editor) {
|
||||
selFrames.insert(current_editor->frame(),
|
||||
current_editor->frame());
|
||||
}
|
||||
else
|
||||
selFrames.insert(0, sprite->lastFrame());
|
||||
}
|
||||
else if (frameTagName != kAllFrames) {
|
||||
frameTag = sprite->frameTags().getByName(frameTagName);
|
||||
if (frameTag)
|
||||
selFrames.insert(frameTag->fromFrame(),
|
||||
frameTag->toFrame());
|
||||
else
|
||||
selFrames.insert(0, sprite->lastFrame());
|
||||
}
|
||||
else
|
||||
selFrames.insert(0, sprite->lastFrame());
|
||||
|
||||
return frameTag;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class ExportSpriteSheetWindow : public app::gen::ExportSpriteSheet {
|
||||
public:
|
||||
class LayerItem : public ListItem {
|
||||
public:
|
||||
LayerItem(Layer* layer)
|
||||
: ListItem(buildName(layer))
|
||||
, m_layer(layer) {
|
||||
}
|
||||
Layer* layer() const { return m_layer; }
|
||||
private:
|
||||
static std::string buildName(const Layer* layer) {
|
||||
bool isGroup = layer->isGroup();
|
||||
std::string name;
|
||||
while (layer != layer->sprite()->root()) {
|
||||
if (!name.empty())
|
||||
name.insert(0, " > ");
|
||||
name.insert(0, layer->name());
|
||||
layer = layer->parent();
|
||||
}
|
||||
name.insert(0, isGroup ? "Group: ": "Layer: ");
|
||||
return name;
|
||||
}
|
||||
Layer* m_layer;
|
||||
};
|
||||
|
||||
class TagItem : public ListItem {
|
||||
public:
|
||||
TagItem(FrameTag* tag)
|
||||
: ListItem("Tag: " + tag->name())
|
||||
, m_tag(tag) {
|
||||
}
|
||||
FrameTag* tag() const { return m_tag; }
|
||||
private:
|
||||
FrameTag* m_tag;
|
||||
};
|
||||
|
||||
ExportSpriteSheetWindow(Document* doc, Sprite* sprite,
|
||||
DocumentPreferences& docPref)
|
||||
: m_sprite(sprite)
|
||||
ExportSpriteSheetWindow(Site& site, DocumentPreferences& docPref)
|
||||
: m_site(site)
|
||||
, m_sprite(site.sprite())
|
||||
, m_docPref(docPref)
|
||||
, m_filenameAskOverwrite(true)
|
||||
, m_dataFilenameAskOverwrite(true)
|
||||
@ -249,29 +180,11 @@ public:
|
||||
if (m_docPref.spriteSheet.type() != app::SpriteSheetType::None)
|
||||
sheetType()->setSelectedItemIndex((int)m_docPref.spriteSheet.type()-1);
|
||||
|
||||
layers()->addItem("Visible layers");
|
||||
int i = layers()->addItem("Selected layers");
|
||||
if (m_docPref.spriteSheet.layer() == kSelectedLayers)
|
||||
layers()->setSelectedItemIndex(i);
|
||||
{
|
||||
LayerList layersList = m_sprite->allLayers();
|
||||
for (auto it=layersList.rbegin(), end=layersList.rend(); it!=end; ++it) {
|
||||
Layer* layer = *it;
|
||||
i = layers()->addItem(new LayerItem(layer));
|
||||
if (m_docPref.spriteSheet.layer() == layer->name())
|
||||
layers()->setSelectedItemIndex(i);
|
||||
}
|
||||
}
|
||||
fill_layers_combobox(
|
||||
m_sprite, layers(), m_docPref.spriteSheet.layer());
|
||||
|
||||
frames()->addItem("All frames");
|
||||
i = frames()->addItem("Selected frames");
|
||||
if (m_docPref.spriteSheet.frameTag() == kSelectedFrames)
|
||||
frames()->setSelectedItemIndex(i);
|
||||
for (FrameTag* tag : m_sprite->frameTags()) {
|
||||
i = frames()->addItem(new TagItem(tag));
|
||||
if (m_docPref.spriteSheet.frameTag() == tag->name())
|
||||
frames()->setSelectedItemIndex(i);
|
||||
}
|
||||
fill_frames_combobox(
|
||||
m_sprite, frames(), m_docPref.spriteSheet.frameTag());
|
||||
|
||||
openGenerated()->setSelected(m_docPref.spriteSheet.openGenerated());
|
||||
|
||||
@ -321,12 +234,12 @@ public:
|
||||
listTags()->setSelected(m_docPref.spriteSheet.listFrameTags());
|
||||
updateDataFields();
|
||||
|
||||
std::string base = doc->filename();
|
||||
std::string base = site.document()->filename();
|
||||
base = base::join_path(base::get_file_path(base), base::get_file_title(base));
|
||||
|
||||
if (m_filename.empty() ||
|
||||
m_filename == kSpecifiedFilename) {
|
||||
if (base::utf8_icmp(base::get_file_extension(doc->filename()), "png") == 0)
|
||||
if (base::utf8_icmp(base::get_file_extension(site.document()->filename()), "png") == 0)
|
||||
m_filename = base + "-sheet.png";
|
||||
else
|
||||
m_filename = base + ".png";
|
||||
@ -446,21 +359,11 @@ public:
|
||||
}
|
||||
|
||||
std::string layerValue() const {
|
||||
if (LayerItem* item = dynamic_cast<LayerItem*>(layers()->getSelectedItem()))
|
||||
return item->layer()->name();
|
||||
else if (layers()->getSelectedItemIndex() == 1)
|
||||
return kSelectedLayers;
|
||||
else
|
||||
return kAllLayers;
|
||||
return layers()->getValue();
|
||||
}
|
||||
|
||||
std::string frameTagValue() const {
|
||||
if (TagItem* item = dynamic_cast<TagItem*>(frames()->getSelectedItem()))
|
||||
return item->tag()->name();
|
||||
else if (frames()->getSelectedItemIndex() == 1)
|
||||
return kSelectedFrames;
|
||||
else
|
||||
return kAllFrames;
|
||||
return frames()->getValue();
|
||||
}
|
||||
|
||||
bool listLayersValue() const {
|
||||
@ -613,7 +516,7 @@ private:
|
||||
|
||||
void updateSizeFields() {
|
||||
SelectedFrames selFrames;
|
||||
calculate_selected_frames(m_sprite,
|
||||
calculate_selected_frames(m_site,
|
||||
frameTagValue(),
|
||||
selFrames);
|
||||
|
||||
@ -646,6 +549,7 @@ private:
|
||||
dataMeta()->setVisible(state);
|
||||
}
|
||||
|
||||
Site& m_site;
|
||||
Sprite* m_sprite;
|
||||
DocumentPreferences& m_docPref;
|
||||
std::string m_filename;
|
||||
@ -700,13 +604,14 @@ bool ExportSpriteSheetCommand::onEnabled(Context* context)
|
||||
|
||||
void ExportSpriteSheetCommand::onExecute(Context* context)
|
||||
{
|
||||
Document* document(context->activeDocument());
|
||||
Sprite* sprite = document->sprite();
|
||||
Site site = context->activeSite();
|
||||
Document* document = static_cast<Document*>(site.document());
|
||||
Sprite* sprite = site.sprite();
|
||||
DocumentPreferences& docPref(Preferences::instance().document(document));
|
||||
bool askOverwrite = m_askOverwrite;
|
||||
|
||||
if (m_useUI && context->isUIAvailable()) {
|
||||
ExportSpriteSheetWindow window(document, sprite, docPref);
|
||||
ExportSpriteSheetWindow window(site, docPref);
|
||||
window.openWindowInForeground();
|
||||
if (!window.ok())
|
||||
return;
|
||||
@ -771,7 +676,7 @@ void ExportSpriteSheetCommand::onExecute(Context* context)
|
||||
|
||||
SelectedFrames selFrames;
|
||||
FrameTag* frameTag =
|
||||
calculate_selected_frames(sprite, frameTagName, selFrames);
|
||||
calculate_selected_frames(site, frameTagName, selFrames);
|
||||
|
||||
frame_t nframes = selFrames.size();
|
||||
ASSERT(nframes > 0);
|
||||
@ -779,16 +684,10 @@ void ExportSpriteSheetCommand::onExecute(Context* context)
|
||||
// If the user choose to render selected layers only, we can
|
||||
// temporaly make them visible and hide the other ones.
|
||||
RestoreVisibleLayers layersVisibility;
|
||||
calculate_visible_layers(site, layerName, layersVisibility);
|
||||
|
||||
SelectedLayers selLayers;
|
||||
if (layerName == kSelectedLayers) {
|
||||
// TODO the range of selected frames should be in doc::Site.
|
||||
auto range = App::instance()->timeline()->range();
|
||||
if (range.enabled())
|
||||
layersVisibility.showSelectedLayers(sprite, range.selectedLayers());
|
||||
else if (current_editor)
|
||||
layersVisibility.showLayer(current_editor->layer());
|
||||
}
|
||||
else {
|
||||
if (layerName != kSelectedLayers) {
|
||||
// TODO add a getLayerByName
|
||||
for (Layer* layer : sprite->allLayers()) {
|
||||
if (layer->name() == layerName) {
|
||||
|
@ -22,6 +22,8 @@
|
||||
#include "app/modules/gui.h"
|
||||
#include "app/pref/preferences.h"
|
||||
#include "app/recent_files.h"
|
||||
#include "app/restore_visible_layers.h"
|
||||
#include "app/ui/layer_frame_comboboxes.h"
|
||||
#include "app/ui/status_bar.h"
|
||||
#include "base/bind.h"
|
||||
#include "base/convert_to.h"
|
||||
@ -29,6 +31,7 @@
|
||||
#include "base/path.h"
|
||||
#include "base/thread.h"
|
||||
#include "base/unique_ptr.h"
|
||||
#include "doc/frame_tag.h"
|
||||
#include "doc/sprite.h"
|
||||
#include "ui/ui.h"
|
||||
|
||||
@ -36,8 +39,14 @@ namespace app {
|
||||
|
||||
class SaveAsCopyDelegate : public FileSelectorDelegate {
|
||||
public:
|
||||
SaveAsCopyDelegate(double scale)
|
||||
: m_resizeScale(scale) { }
|
||||
SaveAsCopyDelegate(const Sprite* sprite,
|
||||
const double scale,
|
||||
const std::string& layer,
|
||||
const std::string& frame)
|
||||
: m_sprite(sprite),
|
||||
m_resizeScale(scale),
|
||||
m_layer(layer),
|
||||
m_frame(frame) { }
|
||||
|
||||
bool hasResizeCombobox() override {
|
||||
return true;
|
||||
@ -51,8 +60,30 @@ public:
|
||||
m_resizeScale = scale;
|
||||
}
|
||||
|
||||
void fillLayersComboBox(ui::ComboBox* layers) override {
|
||||
fill_layers_combobox(m_sprite, layers, m_layer);
|
||||
}
|
||||
|
||||
void fillFramesComboBox(ui::ComboBox* frames) override {
|
||||
fill_frames_combobox(m_sprite, frames, m_frame);
|
||||
}
|
||||
|
||||
std::string getLayers() override { return m_layer; }
|
||||
std::string getFrames() override { return m_frame; }
|
||||
|
||||
void setLayers(const std::string& layer) override {
|
||||
m_layer = layer;
|
||||
}
|
||||
|
||||
void setFrames(const std::string& frame) override {
|
||||
m_frame = frame;
|
||||
}
|
||||
|
||||
private:
|
||||
const Sprite* m_sprite;
|
||||
double m_resizeScale;
|
||||
std::string m_layer;
|
||||
std::string m_frame;
|
||||
};
|
||||
|
||||
class SaveFileJob : public Job, public IFileOpProgress {
|
||||
@ -106,11 +137,16 @@ void SaveFileBaseCommand::onLoadParams(const Params& params)
|
||||
m_filenameFormat = params.get("filename-format");
|
||||
m_frameTag = params.get("frame-tag");
|
||||
|
||||
m_fromFrame = m_toFrame = -1;
|
||||
if (params.has_param("from-frame") ||
|
||||
params.has_param("to-frame")) {
|
||||
m_fromFrame = params.get_as<doc::frame_t>("from-frame");
|
||||
m_toFrame = params.get_as<doc::frame_t>("to-frame");
|
||||
doc::frame_t fromFrame = params.get_as<doc::frame_t>("from-frame");
|
||||
doc::frame_t toFrame = params.get_as<doc::frame_t>("to-frame");
|
||||
m_selFrames.insert(fromFrame, toFrame);
|
||||
m_adjustFramesByFrameTag = true;
|
||||
}
|
||||
else {
|
||||
m_selFrames.clear();
|
||||
m_adjustFramesByFrameTag = false;
|
||||
}
|
||||
}
|
||||
|
||||
@ -191,8 +227,29 @@ bool SaveFileBaseCommand::saveAsDialog(Context* context,
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
RestoreVisibleLayers layersVisibility;
|
||||
if (delegate) {
|
||||
Site site = context->activeSite();
|
||||
|
||||
// Selected layers to export
|
||||
calculate_visible_layers(site,
|
||||
delegate->getLayers(),
|
||||
layersVisibility);
|
||||
|
||||
// Selected frames to export
|
||||
SelectedFrames selFrames;
|
||||
FrameTag* frameTag = calculate_selected_frames(
|
||||
site, delegate->getFrames(), selFrames);
|
||||
if (frameTag)
|
||||
m_frameTag = frameTag->name();
|
||||
m_selFrames = selFrames;
|
||||
m_adjustFramesByFrameTag = false;
|
||||
}
|
||||
|
||||
// Save the document
|
||||
saveDocumentInBackground(context, const_cast<Document*>(document), markAsSaved);
|
||||
}
|
||||
|
||||
// Undo resize
|
||||
if (undoResize) {
|
||||
@ -221,7 +278,8 @@ void SaveFileBaseCommand::saveDocumentInBackground(const Context* context,
|
||||
base::UniquePtr<FileOp> fop(
|
||||
FileOp::createSaveDocumentOperation(
|
||||
context,
|
||||
FileOpROI(document, m_frameTag, m_fromFrame, m_toFrame),
|
||||
FileOpROI(document, m_frameTag,
|
||||
m_selFrames, m_adjustFramesByFrameTag),
|
||||
document->filename().c_str(),
|
||||
m_filenameFormat.c_str()));
|
||||
if (!fop)
|
||||
@ -331,7 +389,10 @@ void SaveFileCopyAsCommand::onExecute(Context* context)
|
||||
|
||||
// show "Save As" dialog
|
||||
DocumentPreferences& docPref = Preferences::instance().document(document);
|
||||
SaveAsCopyDelegate delegate(docPref.saveCopy.resizeScale());
|
||||
SaveAsCopyDelegate delegate(document->sprite(),
|
||||
docPref.saveCopy.resizeScale(),
|
||||
docPref.saveCopy.layer(),
|
||||
docPref.saveCopy.frameTag());
|
||||
|
||||
// Is a default output filename in the preferences?
|
||||
if (!docPref.saveCopy.filename().empty()) {
|
||||
@ -343,6 +404,8 @@ void SaveFileCopyAsCommand::onExecute(Context* context)
|
||||
if (saveAsDialog(context, "Save Copy As", &delegate)) {
|
||||
docPref.saveCopy.filename(document->filename());
|
||||
docPref.saveCopy.resizeScale(delegate.getResizeScale());
|
||||
docPref.saveCopy.layer(delegate.getLayers());
|
||||
docPref.saveCopy.frameTag(delegate.getFrames());
|
||||
}
|
||||
|
||||
// Restore the file name.
|
||||
|
@ -9,7 +9,7 @@
|
||||
#pragma once
|
||||
|
||||
#include "app/commands/command.h"
|
||||
#include "doc/frame.h"
|
||||
#include "doc/selected_frames.h"
|
||||
|
||||
#include <string>
|
||||
|
||||
@ -39,7 +39,8 @@ namespace app {
|
||||
std::string m_filenameFormat;
|
||||
std::string m_selectedFilename;
|
||||
std::string m_frameTag;
|
||||
doc::frame_t m_fromFrame, m_toFrame;
|
||||
doc::SelectedFrames m_selFrames;
|
||||
bool m_adjustFramesByFrameTag;
|
||||
};
|
||||
|
||||
} // namespace app
|
||||
|
@ -405,8 +405,8 @@ bool AseFormat::onSave(FileOp* fop)
|
||||
}
|
||||
|
||||
// Write frames
|
||||
for (frame_t frame=fop->roi().fromFrame();
|
||||
frame <= fop->roi().toFrame(); ++frame) {
|
||||
int outputFrame = 0;
|
||||
for (frame_t frame : fop->roi().selectedFrames()) {
|
||||
// Prepare the frame header
|
||||
ASE_FrameHeader frame_header;
|
||||
ase_file_prepare_frame_header(f, &frame_header);
|
||||
@ -454,7 +454,8 @@ bool AseFormat::onSave(FileOp* fop)
|
||||
|
||||
// Progress
|
||||
if (fop->roi().frames() > 1)
|
||||
fop->setProgress(float(frame+1) / float(fop->roi().frames()));
|
||||
fop->setProgress(float(outputFrame+1) / float(fop->roi().frames()));
|
||||
++outputFrame;
|
||||
|
||||
if (fop->isStop())
|
||||
break;
|
||||
|
@ -102,7 +102,8 @@ int save_document(Context* context, doc::Document* document)
|
||||
UniquePtr<FileOp> fop(
|
||||
FileOp::createSaveDocumentOperation(
|
||||
context,
|
||||
FileOpROI(static_cast<app::Document*>(document), "", -1, -1),
|
||||
FileOpROI(static_cast<app::Document*>(document), "",
|
||||
SelectedFrames(), false),
|
||||
document->filename().c_str(), ""));
|
||||
if (!fop)
|
||||
return -1;
|
||||
@ -135,47 +136,30 @@ bool is_static_image_format(const std::string& filename)
|
||||
FileOpROI::FileOpROI()
|
||||
: m_document(nullptr)
|
||||
, m_frameTag(nullptr)
|
||||
, m_fromFrame(-1)
|
||||
, m_toFrame(-1)
|
||||
{
|
||||
}
|
||||
|
||||
FileOpROI::FileOpROI(const app::Document* doc,
|
||||
const std::string& frameTagName,
|
||||
const doc::frame_t fromFrame,
|
||||
const doc::frame_t toFrame)
|
||||
const doc::SelectedFrames selFrames,
|
||||
const bool adjustByFrameTag)
|
||||
: m_document(doc)
|
||||
, m_frameTag(nullptr)
|
||||
, m_fromFrame(fromFrame)
|
||||
, m_toFrame(toFrame)
|
||||
, m_selFrames(selFrames)
|
||||
{
|
||||
if (doc) {
|
||||
if (fromFrame >= 0)
|
||||
m_fromFrame = MID(0, fromFrame, doc->sprite()->lastFrame());
|
||||
else
|
||||
m_fromFrame = 0;
|
||||
m_frameTag = doc->sprite()->frameTags().getByName(frameTagName);
|
||||
if (m_frameTag) {
|
||||
if (adjustByFrameTag)
|
||||
m_selFrames.displace(m_frameTag->fromFrame());
|
||||
|
||||
if (toFrame >= 0)
|
||||
m_toFrame = MID(m_fromFrame, toFrame, doc->sprite()->lastFrame());
|
||||
else
|
||||
m_toFrame = doc->sprite()->lastFrame();
|
||||
|
||||
if (!frameTagName.empty()) {
|
||||
doc::FrameTag* tag = doc->sprite()->frameTags().getByName(frameTagName);
|
||||
if (tag) {
|
||||
m_frameTag = tag;
|
||||
|
||||
if (fromFrame >= 0)
|
||||
m_fromFrame = tag->fromFrame() + MID(0, fromFrame, tag->frames()-1);
|
||||
else
|
||||
m_fromFrame = tag->fromFrame();
|
||||
|
||||
if (toFrame >= 0)
|
||||
m_toFrame = tag->fromFrame() + MID(fromFrame, toFrame, tag->frames()-1);
|
||||
else
|
||||
m_toFrame = tag->toFrame();
|
||||
}
|
||||
m_selFrames.filter(MAX(0, m_frameTag->fromFrame()),
|
||||
MIN(m_frameTag->toFrame(),
|
||||
doc->sprite()->lastFrame()));
|
||||
}
|
||||
// All frames if selected frames is empty
|
||||
else if (m_selFrames.empty())
|
||||
m_selFrames.insert(0, doc->sprite()->lastFrame());
|
||||
}
|
||||
}
|
||||
|
||||
@ -453,9 +437,9 @@ FileOp* FileOp::createSaveDocumentOperation(const Context* context,
|
||||
}
|
||||
|
||||
Sprite* spr = fop->m_document->sprite();
|
||||
frame_t outputFrame = 0;
|
||||
|
||||
for (frame_t frame = fop->m_roi.fromFrame();
|
||||
frame <= fop->m_roi.toFrame(); ++frame) {
|
||||
for (frame_t frame : fop->m_roi.selectedFrames()) {
|
||||
FrameTag* innerTag = (fop->m_roi.frameTag() ? fop->m_roi.frameTag(): spr->frameTags().innerTag(frame));
|
||||
FrameTag* outerTag = (fop->m_roi.frameTag() ? fop->m_roi.frameTag(): spr->frameTags().outerTag(frame));
|
||||
FilenameInfo fnInfo;
|
||||
@ -463,12 +447,14 @@ FileOp* FileOp::createSaveDocumentOperation(const Context* context,
|
||||
.filename(fn)
|
||||
.innerTagName(innerTag ? innerTag->name(): "")
|
||||
.outerTagName(outerTag ? outerTag->name(): "")
|
||||
.frame(frame - fop->m_roi.fromFrame())
|
||||
.frame(outputFrame)
|
||||
.tagFrame(innerTag ? frame - innerTag->fromFrame():
|
||||
frame);
|
||||
outputFrame);
|
||||
|
||||
fop->m_seq.filename_list.push_back(
|
||||
filename_formatter(fn_format, fnInfo));
|
||||
|
||||
++outputFrame;
|
||||
}
|
||||
|
||||
if (context && context->isUIAvailable() &&
|
||||
@ -655,8 +641,8 @@ void FileOp::operate(IFileOpProgress* progress)
|
||||
|
||||
// For each frame in the sprite.
|
||||
render::Render render;
|
||||
for (frame_t frame = m_roi.fromFrame();
|
||||
frame <= m_roi.toFrame(); ++frame) {
|
||||
frame_t outputFrame = 0;
|
||||
for (frame_t frame : m_roi.selectedFrames()) {
|
||||
// Draw the "frame" in "m_seq.image"
|
||||
render.renderSprite(m_seq.image.get(), sprite, frame);
|
||||
|
||||
@ -664,16 +650,17 @@ void FileOp::operate(IFileOpProgress* progress)
|
||||
sprite->palette(frame)->copyColorsTo(m_seq.palette);
|
||||
|
||||
// Setup the filename to be used.
|
||||
m_filename = m_seq.filename_list[frame - m_roi.fromFrame()];
|
||||
m_filename = m_seq.filename_list[outputFrame];
|
||||
|
||||
// Call the "save" procedure... did it fail?
|
||||
if (!m_format->save(this)) {
|
||||
setError("Error saving frame %d in the file \"%s\"\n",
|
||||
frame+1, m_filename.c_str());
|
||||
outputFrame+1, m_filename.c_str());
|
||||
break;
|
||||
}
|
||||
|
||||
m_seq.progress_offset += m_seq.progress_fraction;
|
||||
++outputFrame;
|
||||
}
|
||||
|
||||
m_filename = *m_seq.filename_list.begin();
|
||||
|
@ -13,6 +13,7 @@
|
||||
#include "doc/frame.h"
|
||||
#include "doc/image_ref.h"
|
||||
#include "doc/pixel_format.h"
|
||||
#include "doc/selected_frames.h"
|
||||
|
||||
#include <cstdio>
|
||||
#include <string>
|
||||
@ -63,25 +64,23 @@ namespace app {
|
||||
FileOpROI();
|
||||
FileOpROI(const app::Document* doc,
|
||||
const std::string& frameTagName,
|
||||
const doc::frame_t fromFrame,
|
||||
const doc::frame_t toFrame);
|
||||
const doc::SelectedFrames selFrames,
|
||||
const bool adjustByFrameTag);
|
||||
|
||||
const app::Document* document() const { return m_document; }
|
||||
doc::FrameTag* frameTag() const { return m_frameTag; }
|
||||
doc::frame_t fromFrame() const { return m_fromFrame; }
|
||||
doc::frame_t toFrame() const { return m_toFrame; }
|
||||
doc::frame_t fromFrame() const { return m_selFrames.firstFrame(); }
|
||||
doc::frame_t toFrame() const { return m_selFrames.lastFrame(); }
|
||||
const doc::SelectedFrames& selectedFrames() const { return m_selFrames; }
|
||||
|
||||
doc::frame_t frames() const {
|
||||
ASSERT(m_fromFrame >= 0);
|
||||
ASSERT(m_toFrame >= 0);
|
||||
return (m_toFrame - m_fromFrame + 1);
|
||||
return m_selFrames.size();
|
||||
}
|
||||
|
||||
private:
|
||||
const app::Document* m_document;
|
||||
doc::FrameTag* m_frameTag;
|
||||
doc::frame_t m_fromFrame;
|
||||
doc::frame_t m_toFrame;
|
||||
doc::SelectedFrames m_selFrames;
|
||||
};
|
||||
|
||||
// Structure to load & save files.
|
||||
|
@ -156,25 +156,30 @@ bool FliFormat::onLoad(FileOp* fop)
|
||||
#ifdef ENABLE_SAVE
|
||||
|
||||
static int get_time_precision(const Sprite* sprite,
|
||||
const doc::frame_t fromFrame,
|
||||
const doc::frame_t toFrame)
|
||||
const doc::SelectedFrames& selFrames)
|
||||
{
|
||||
// Check if all frames have the same duration
|
||||
bool constantFrameRate = true;
|
||||
for (frame_t c=fromFrame+1; c <= toFrame; ++c) {
|
||||
if (sprite->frameDuration(c-1) != sprite->frameDuration(c)) {
|
||||
frame_t prevFrame = -1;
|
||||
for (frame_t frame : selFrames) {
|
||||
if (prevFrame >= 0) {
|
||||
if (sprite->frameDuration(prevFrame) != sprite->frameDuration(frame)) {
|
||||
constantFrameRate = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
prevFrame = frame;
|
||||
}
|
||||
if (constantFrameRate)
|
||||
return sprite->frameDuration(0);
|
||||
|
||||
int precision = 1000;
|
||||
for (frame_t c=fromFrame; c <= toFrame && precision > 1; ++c) {
|
||||
int len = sprite->frameDuration(c);
|
||||
for (frame_t frame : selFrames) {
|
||||
int len = sprite->frameDuration(frame);
|
||||
while (len / precision == 0)
|
||||
precision /= 10;
|
||||
if (precision <= 1)
|
||||
break;
|
||||
}
|
||||
return precision;
|
||||
}
|
||||
@ -193,10 +198,7 @@ bool FliFormat::onSave(FileOp* fop)
|
||||
header.frames = 0;
|
||||
header.width = sprite->width();
|
||||
header.height = sprite->height();
|
||||
header.speed = get_time_precision(
|
||||
sprite,
|
||||
fop->roi().fromFrame(),
|
||||
fop->roi().toFrame());
|
||||
header.speed = get_time_precision(sprite, fop->roi().selectedFrames());
|
||||
encoder.writeHeader(header);
|
||||
|
||||
// Create the bitmaps
|
||||
@ -207,9 +209,16 @@ bool FliFormat::onSave(FileOp* fop)
|
||||
flic::Frame fliFrame;
|
||||
fliFrame.pixels = bmp->getPixelAddress(0, 0);
|
||||
fliFrame.rowstride = IndexedTraits::getRowStrideBytes(bmp->width());
|
||||
|
||||
auto frame_beg = fop->roi().selectedFrames().begin();
|
||||
auto frame_end = fop->roi().selectedFrames().end();
|
||||
auto frame_it = frame_beg;
|
||||
frame_t nframes = fop->roi().frames();
|
||||
for (frame_t frame_it=0; frame_it <= nframes; ++frame_it) {
|
||||
frame_t frame = fop->roi().fromFrame() + (frame_it % nframes);
|
||||
for (frame_t f=0; f<=nframes; ++f, ++frame_it) {
|
||||
if (frame_it == frame_end)
|
||||
frame_it = frame_beg;
|
||||
|
||||
frame_t frame = *frame_it;
|
||||
const Palette* pal = sprite->palette(frame);
|
||||
int size = MIN(256, pal->size());
|
||||
|
||||
@ -225,7 +234,7 @@ bool FliFormat::onSave(FileOp* fop)
|
||||
|
||||
// How many times this frame should be written to get the same
|
||||
// time that it has in the sprite
|
||||
if (frame_it < nframes) {
|
||||
if (f < nframes) {
|
||||
int times = sprite->frameDuration(frame) / header.speed;
|
||||
times = MAX(1, times);
|
||||
for (int c=0; c<times; c++)
|
||||
@ -236,7 +245,7 @@ bool FliFormat::onSave(FileOp* fop)
|
||||
}
|
||||
|
||||
// Update progress
|
||||
fop->setProgress((float)(frame_it+1) / (float)(nframes+1));
|
||||
fop->setProgress((float)(f+1) / (float)(nframes+1));
|
||||
}
|
||||
|
||||
return true;
|
||||
|
@ -814,6 +814,8 @@ bool GifFormat::onLoad(FileOp* fop)
|
||||
|
||||
class GifEncoder {
|
||||
public:
|
||||
typedef int gifframe_t;
|
||||
|
||||
GifEncoder(FileOp* fop, GifFileType* gifFile)
|
||||
: m_fop(fop)
|
||||
, m_gifFile(gifFile)
|
||||
@ -894,27 +896,37 @@ public:
|
||||
m_currentImage = m_images[1].get();
|
||||
m_nextImage = m_images[2].get();
|
||||
|
||||
int nframes = totalFrames();
|
||||
for (int frameNum=0; frameNum<nframes; ++frameNum) {
|
||||
if (frameNum == 0)
|
||||
renderFrame(0, m_nextImage);
|
||||
else if (frameNum > 0)
|
||||
auto frame_beg = m_fop->roi().selectedFrames().begin();
|
||||
auto frame_end = m_fop->roi().selectedFrames().end();
|
||||
auto frame_it = frame_beg;
|
||||
|
||||
// In this code "gifFrame" will be the GIF frame, and "frame" will
|
||||
// be the doc::Sprite frame.
|
||||
gifframe_t nframes = totalFrames();
|
||||
for (gifframe_t gifFrame=0; gifFrame<nframes; ++gifFrame) {
|
||||
ASSERT(frame_it != frame_end);
|
||||
frame_t frame = *frame_it;
|
||||
++frame_it;
|
||||
|
||||
if (gifFrame == 0)
|
||||
renderFrame(frame, m_nextImage);
|
||||
else
|
||||
std::swap(m_previousImage, m_currentImage);
|
||||
|
||||
// Render next frame
|
||||
std::swap(m_currentImage, m_nextImage);
|
||||
if (frameNum+1 < nframes)
|
||||
renderFrame(frameNum+1, m_nextImage);
|
||||
if (gifFrame+1 < nframes)
|
||||
renderFrame(*frame_it, m_nextImage);
|
||||
|
||||
gfx::Rect frameBounds;
|
||||
DisposalMethod disposal;
|
||||
calculateBestDisposalMethod(
|
||||
frameNum, frameBounds, disposal);
|
||||
calculateBestDisposalMethod(gifFrame, frameBounds, disposal);
|
||||
|
||||
// TODO We could join both frames in a longer one (with more duration)
|
||||
if (frameBounds.isEmpty())
|
||||
frameBounds = gfx::Rect(0, 0, 1, 1);
|
||||
|
||||
writeImage(frameNum, frameBounds, disposal);
|
||||
writeImage(gifFrame, frame, frameBounds, disposal);
|
||||
|
||||
// Dispose/clear frame content
|
||||
process_disposal_method(m_previousImage,
|
||||
@ -923,7 +935,7 @@ public:
|
||||
frameBounds,
|
||||
m_clearColor);
|
||||
|
||||
m_fop->setProgress(double(frameNum+1) / double(nframes));
|
||||
m_fop->setProgress(double(gifFrame+1) / double(nframes));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@ -934,10 +946,6 @@ private:
|
||||
return m_fop->roi().frames();
|
||||
}
|
||||
|
||||
doc::frame_t fromFrame() const {
|
||||
return m_fop->roi().fromFrame();
|
||||
}
|
||||
|
||||
void writeHeader() {
|
||||
if (EGifPutScreenDesc(m_gifFile,
|
||||
m_spriteBounds.w,
|
||||
@ -986,9 +994,9 @@ private:
|
||||
|
||||
// Writes graphics extension record (to save the duration of the
|
||||
// frame and maybe the transparency index).
|
||||
void writeExtension(int frameNum, int transparentIndex, DisposalMethod disposalMethod) {
|
||||
void writeExtension(gifframe_t gifFrame, frame_t frame, int transparentIndex, DisposalMethod disposalMethod) {
|
||||
unsigned char extension_bytes[5];
|
||||
int frameDelay = m_sprite->frameDuration(fromFrame()+frameNum) / 10;
|
||||
int frameDelay = m_sprite->frameDuration(frame) / 10;
|
||||
|
||||
extension_bytes[0] = (((int(disposalMethod) & 7) << 2) |
|
||||
(transparentIndex >= 0 ? 1: 0));
|
||||
@ -997,7 +1005,7 @@ private:
|
||||
extension_bytes[3] = (transparentIndex >= 0 ? transparentIndex: 0);
|
||||
|
||||
if (EGifPutExtension(m_gifFile, GRAPHICS_EXT_FUNC_CODE, 4, extension_bytes) == GIF_ERROR)
|
||||
throw Exception("Error writing GIF graphics extension record for frame %d.\n", (int)frameNum);
|
||||
throw Exception("Error writing GIF graphics extension record for frame %d.\n", gifFrame);
|
||||
}
|
||||
|
||||
static gfx::Rect calculateFrameBounds(Image* a, Image* b) {
|
||||
@ -1014,8 +1022,7 @@ private:
|
||||
return frameBounds;
|
||||
}
|
||||
|
||||
void calculateBestDisposalMethod(int frameNum,
|
||||
gfx::Rect& frameBounds,
|
||||
void calculateBestDisposalMethod(gifframe_t gifFrame, gfx::Rect& frameBounds,
|
||||
DisposalMethod& disposal) {
|
||||
if (m_hasBackground) {
|
||||
disposal = DisposalMethod::DO_NOT_DISPOSE;
|
||||
@ -1024,17 +1031,17 @@ private:
|
||||
disposal = DisposalMethod::RESTORE_BGCOLOR;
|
||||
}
|
||||
|
||||
if (frameNum == 0) {
|
||||
if (gifFrame == 0) {
|
||||
frameBounds = m_spriteBounds;
|
||||
}
|
||||
else {
|
||||
gfx::Rect prev, next;
|
||||
|
||||
if (frameNum-1 >= 0)
|
||||
if (gifFrame-1 >= 0)
|
||||
prev = calculateFrameBounds(m_currentImage, m_previousImage);
|
||||
|
||||
if (!m_hasBackground &&
|
||||
frameNum+1 < totalFrames())
|
||||
gifFrame+1 < totalFrames())
|
||||
next = calculateFrameBounds(m_currentImage, m_nextImage);
|
||||
|
||||
frameBounds = prev.createUnion(next);
|
||||
@ -1058,11 +1065,11 @@ private:
|
||||
}
|
||||
}
|
||||
|
||||
void writeImage(int frameNum, const gfx::Rect& frameBounds, DisposalMethod disposal) {
|
||||
void writeImage(gifframe_t gifFrame, frame_t frame, const gfx::Rect& frameBounds, DisposalMethod disposal) {
|
||||
UniquePtr<Palette> framePaletteRef;
|
||||
UniquePtr<RgbMap> rgbmapRef;
|
||||
Palette* framePalette = m_sprite->palette(fromFrame()+frameNum);
|
||||
RgbMap* rgbmap = m_sprite->rgbMap(fromFrame()+frameNum);
|
||||
Palette* framePalette = m_sprite->palette(frame);
|
||||
RgbMap* rgbmap = m_sprite->rgbMap(frame);
|
||||
|
||||
// Create optimized palette for RGB/Grayscale images
|
||||
if (m_quantizeColormaps) {
|
||||
@ -1154,7 +1161,7 @@ private:
|
||||
int localTransparent = m_transparentIndex;
|
||||
ColorMapObject* colormap = m_globalColormap;
|
||||
if (!colormap) {
|
||||
Palette reducedPalette(frameNum, usedNColors);
|
||||
Palette reducedPalette(0, usedNColors);
|
||||
|
||||
for (int i=0, j=0; i<framePalette->size(); ++i) {
|
||||
if (usedColors[i]) {
|
||||
@ -1173,7 +1180,7 @@ private:
|
||||
remap.map(m_transparentIndex, localTransparent);
|
||||
|
||||
// Write extension record.
|
||||
writeExtension(frameNum, localTransparent, disposal);
|
||||
writeExtension(gifFrame, frame, localTransparent, disposal);
|
||||
|
||||
// Write the image record.
|
||||
if (EGifPutImageDesc(m_gifFile,
|
||||
@ -1181,7 +1188,7 @@ private:
|
||||
frameBounds.w, frameBounds.h,
|
||||
m_interlaced ? 1: 0,
|
||||
(colormap != m_globalColormap ? colormap: nullptr)) == GIF_ERROR) {
|
||||
throw Exception("Error writing GIF frame %d.\n", (int)frameNum);
|
||||
throw Exception("Error writing GIF frame %d.\n", gifFrame);
|
||||
}
|
||||
|
||||
std::vector<uint8_t> scanline(frameBounds.w);
|
||||
@ -1198,7 +1205,7 @@ private:
|
||||
scanline[i] = remap[*addr];
|
||||
|
||||
if (EGifPutLine(m_gifFile, &scanline[0], frameBounds.w) == GIF_ERROR)
|
||||
throw Exception("Error writing GIF image scanlines for frame %d.\n", (int)frameNum);
|
||||
throw Exception("Error writing GIF image scanlines for frame %d.\n", gifFrame);
|
||||
}
|
||||
}
|
||||
else {
|
||||
@ -1211,7 +1218,7 @@ private:
|
||||
scanline[i] = remap[*addr];
|
||||
|
||||
if (EGifPutLine(m_gifFile, &scanline[0], frameBounds.w) == GIF_ERROR)
|
||||
throw Exception("Error writing GIF image scanlines for frame %d.\n", (int)frameNum);
|
||||
throw Exception("Error writing GIF image scanlines for frame %d.\n", gifFrame);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1236,11 +1243,11 @@ private:
|
||||
return palette;
|
||||
}
|
||||
|
||||
void renderFrame(int frameNum, Image* dst) {
|
||||
void renderFrame(frame_t frame, Image* dst) {
|
||||
render::Render render;
|
||||
render.setBgType(render::BgType::NONE);
|
||||
clear_image(dst, m_clearColor);
|
||||
render.renderSprite(dst, m_sprite, fromFrame()+frameNum);
|
||||
render.renderSprite(dst, m_sprite, frame);
|
||||
}
|
||||
|
||||
private:
|
||||
|
@ -1,5 +1,5 @@
|
||||
// Aseprite
|
||||
// Copyright (C) 2001-2015 David Capello
|
||||
// Copyright (C) 2001-2016 David Capello
|
||||
//
|
||||
// This program is distributed under the terms of
|
||||
// the End-User License Agreement for Aseprite.
|
||||
@ -10,6 +10,10 @@
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace ui {
|
||||
class ComboBox;
|
||||
}
|
||||
|
||||
namespace app {
|
||||
|
||||
enum class FileSelectorType { Open, Save };
|
||||
@ -20,6 +24,15 @@ namespace app {
|
||||
virtual bool hasResizeCombobox() = 0;
|
||||
virtual double getResizeScale() = 0;
|
||||
virtual void setResizeScale(double scale) = 0;
|
||||
|
||||
// TODO refactor this to avoid using a ui::ComboBox,
|
||||
// mainly to re-use this in the native file selector
|
||||
virtual void fillLayersComboBox(ui::ComboBox* layers) = 0;
|
||||
virtual void fillFramesComboBox(ui::ComboBox* frames) = 0;
|
||||
virtual std::string getLayers() = 0;
|
||||
virtual std::string getFrames() = 0;
|
||||
virtual void setLayers(const std::string& layers) = 0;
|
||||
virtual void setFrames(const std::string& frames) = 0;
|
||||
};
|
||||
|
||||
std::string show_file_selector(
|
||||
|
@ -28,6 +28,7 @@ void RestoreVisibleLayers::showLayer(Layer* layer)
|
||||
{
|
||||
SelectedLayers selLayers;
|
||||
selLayers.insert(layer);
|
||||
selLayers.propagateSelection();
|
||||
showSelectedLayers(layer->sprite(), selLayers);
|
||||
}
|
||||
|
||||
|
@ -32,6 +32,7 @@
|
||||
#include "ui/ui.h"
|
||||
|
||||
#include "new_folder_window.xml.h"
|
||||
#include "file_selector_extras.xml.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cctype>
|
||||
@ -94,8 +95,7 @@ static void on_exit_delete_navigation_history()
|
||||
delete navigation_history;
|
||||
}
|
||||
|
||||
class CustomFileNameEntry : public ComboBox
|
||||
{
|
||||
class FileSelector::CustomFileNameEntry : public ComboBox {
|
||||
public:
|
||||
CustomFileNameEntry()
|
||||
: m_fileList(nullptr) {
|
||||
@ -143,8 +143,7 @@ private:
|
||||
FileList* m_fileList;
|
||||
};
|
||||
|
||||
class CustomFileNameItem : public ListItem
|
||||
{
|
||||
class FileSelector::CustomFileNameItem : public ListItem {
|
||||
public:
|
||||
CustomFileNameItem(const char* text, IFileItem* fileItem)
|
||||
: ListItem(text)
|
||||
@ -158,8 +157,7 @@ private:
|
||||
IFileItem* m_fileItem;
|
||||
};
|
||||
|
||||
class CustomFolderNameItem : public ListItem
|
||||
{
|
||||
class FileSelector::CustomFolderNameItem : public ListItem {
|
||||
public:
|
||||
CustomFolderNameItem(const char* text)
|
||||
: ListItem(text)
|
||||
@ -174,7 +172,7 @@ public:
|
||||
// (comboboxes) that need to filter Esc key (e.g. to close the
|
||||
// combobox popup). And we cannot pre-add a filter that send that key
|
||||
// to the Manager before it's processed by the combobox filter.
|
||||
class ArrowNavigator : public Widget {
|
||||
class FileSelector::ArrowNavigator : public Widget {
|
||||
public:
|
||||
ArrowNavigator(FileSelector* filesel)
|
||||
: Widget(kGenericWidget)
|
||||
@ -234,9 +232,71 @@ private:
|
||||
FileSelector* m_filesel;
|
||||
};
|
||||
|
||||
class FileSelector::ExtrasWindow : public PopupWindow {
|
||||
public:
|
||||
ExtrasWindow(FileSelectorDelegate* delegate)
|
||||
: PopupWindow("",
|
||||
ClickBehavior::CloseOnClickInOtherWindow,
|
||||
EnterBehavior::CloseOnEnter)
|
||||
, m_delegate(delegate)
|
||||
, m_extras(new gen::FileSelectorExtras) {
|
||||
|
||||
setAutoRemap(false);
|
||||
setBorder(gfx::Border(4*guiscale()));
|
||||
|
||||
addChild(m_extras);
|
||||
m_extras->resize()->setValue(
|
||||
base::convert_to<std::string>(m_delegate->getResizeScale()));
|
||||
|
||||
m_delegate->fillLayersComboBox(m_extras->layers());
|
||||
m_delegate->fillFramesComboBox(m_extras->frames());
|
||||
|
||||
m_extras->resize()->Change.connect(&ExtrasWindow::onUpdateExtras, this);
|
||||
m_extras->layers()->Change.connect(&ExtrasWindow::onUpdateExtras, this);
|
||||
m_extras->frames()->Change.connect(&ExtrasWindow::onUpdateExtras, this);
|
||||
}
|
||||
|
||||
std::string extrasLabel() const {
|
||||
std::string label = "Resize: " + m_extras->resize()->getSelectedItem()->text();
|
||||
|
||||
auto layerItem = m_extras->layers()->getSelectedItem();
|
||||
if (layerItem && !layerItem->getValue().empty())
|
||||
label += ", " + layerItem->text();
|
||||
|
||||
auto frameItem = m_extras->frames()->getSelectedItem();
|
||||
if (frameItem && !frameItem->getValue().empty())
|
||||
label += ", " + frameItem->text();
|
||||
|
||||
return label;
|
||||
}
|
||||
|
||||
double resizeValue() const {
|
||||
return base::convert_to<double>(m_extras->resize()->getValue());
|
||||
}
|
||||
|
||||
std::string layersValue() const {
|
||||
return m_extras->layers()->getValue();
|
||||
}
|
||||
|
||||
std::string framesValue() const {
|
||||
return m_extras->frames()->getValue();
|
||||
}
|
||||
|
||||
obs::signal<void()> UpdateExtras;
|
||||
|
||||
private:
|
||||
void onUpdateExtras() {
|
||||
UpdateExtras();
|
||||
}
|
||||
|
||||
FileSelectorDelegate* m_delegate;
|
||||
gen::FileSelectorExtras* m_extras;
|
||||
};
|
||||
|
||||
FileSelector::FileSelector(FileSelectorType type, FileSelectorDelegate* delegate)
|
||||
: m_type(type)
|
||||
, m_delegate(delegate)
|
||||
, m_extras(nullptr)
|
||||
, m_navigationLocked(false)
|
||||
{
|
||||
SkinTheme* theme = SkinTheme::instance();
|
||||
@ -298,11 +358,19 @@ FileSelector::FileSelector(FileSelectorType type, FileSelectorDelegate* delegate
|
||||
m_fileList->FileAccepted.connect(base::Bind<void>(&FileSelector::onFileListFileAccepted, this));
|
||||
m_fileList->CurrentFolderChanged.connect(base::Bind<void>(&FileSelector::onFileListCurrentFolderChanged, this));
|
||||
|
||||
resizeOptions()->setVisible(withResizeOptions);
|
||||
if (withResizeOptions) {
|
||||
resize()->setValue("1"); // 100% is default
|
||||
resize()->setValue(base::convert_to<std::string>(m_delegate->getResizeScale()));
|
||||
extraOptions()->Click.connect(base::Bind<void>(&FileSelector::onExtraOptions, this));
|
||||
|
||||
m_extras = new ExtrasWindow(m_delegate);
|
||||
m_extras->UpdateExtras.connect(base::Bind<void>(&FileSelector::updateExtraLabel, this));
|
||||
}
|
||||
|
||||
updateExtraLabel();
|
||||
}
|
||||
|
||||
FileSelector::~FileSelector()
|
||||
{
|
||||
delete m_extras;
|
||||
}
|
||||
|
||||
void FileSelector::goBack()
|
||||
@ -586,8 +654,12 @@ again:
|
||||
set_config_string("FileSelect", "CurrentDirectory",
|
||||
lastpath.c_str());
|
||||
|
||||
if (m_delegate && m_delegate->hasResizeCombobox()) {
|
||||
m_delegate->setResizeScale(base::convert_to<double>(resize()->getValue()));
|
||||
if (m_delegate &&
|
||||
m_delegate->hasResizeCombobox() &&
|
||||
m_extras) {
|
||||
m_delegate->setResizeScale(m_extras->resizeValue());
|
||||
m_delegate->setLayers(m_extras->layersValue());
|
||||
m_delegate->setFrames(m_extras->framesValue());
|
||||
}
|
||||
}
|
||||
|
||||
@ -843,6 +915,18 @@ void FileSelector::onFileListCurrentFolderChanged()
|
||||
m_fileName->closeListBox();
|
||||
}
|
||||
|
||||
void FileSelector::onExtraOptions()
|
||||
{
|
||||
ASSERT(m_extras);
|
||||
|
||||
m_extras->remapWindow();
|
||||
gfx::Rect bounds = m_extras->bounds();
|
||||
ui::fit_bounds(BOTTOM, extraOptions()->bounds(), bounds);
|
||||
|
||||
m_extras->moveWindow(bounds);
|
||||
m_extras->openWindow();
|
||||
}
|
||||
|
||||
std::string FileSelector::getSelectedExtension() const
|
||||
{
|
||||
std::string ext = fileType()->getValue();
|
||||
@ -851,4 +935,20 @@ std::string FileSelector::getSelectedExtension() const
|
||||
return ext;
|
||||
}
|
||||
|
||||
void FileSelector::updateExtraLabel()
|
||||
{
|
||||
if (!m_extras) {
|
||||
extraOptions()->setVisible(false);
|
||||
return;
|
||||
}
|
||||
|
||||
extraOptions()->setVisible(true);
|
||||
|
||||
std::string newLabel = m_extras->extrasLabel();
|
||||
if (extraOptions()->text() != newLabel) {
|
||||
extraOptions()->setText(newLabel);
|
||||
extraOptions()->window()->layout();
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace app
|
||||
|
@ -23,7 +23,6 @@ namespace ui {
|
||||
}
|
||||
|
||||
namespace app {
|
||||
class CustomFileNameEntry;
|
||||
class FileList;
|
||||
class FileListView;
|
||||
class IFileItem;
|
||||
@ -31,6 +30,7 @@ namespace app {
|
||||
class FileSelector : public app::gen::FileSelector {
|
||||
public:
|
||||
FileSelector(FileSelectorType type, FileSelectorDelegate* delegate);
|
||||
~FileSelector();
|
||||
|
||||
void goBack();
|
||||
void goForward();
|
||||
@ -55,7 +55,15 @@ namespace app {
|
||||
void onFileListFileSelected();
|
||||
void onFileListFileAccepted();
|
||||
void onFileListCurrentFolderChanged();
|
||||
void onExtraOptions();
|
||||
std::string getSelectedExtension() const;
|
||||
void updateExtraLabel();
|
||||
|
||||
class ArrowNavigator;
|
||||
class CustomFileNameItem;
|
||||
class CustomFolderNameItem;
|
||||
class CustomFileNameEntry;
|
||||
class ExtrasWindow;
|
||||
|
||||
FileSelectorType m_type;
|
||||
FileSelectorDelegate* m_delegate;
|
||||
@ -63,6 +71,7 @@ namespace app {
|
||||
CustomFileNameEntry* m_fileName;
|
||||
FileList* m_fileList;
|
||||
FileListView* m_fileView;
|
||||
ExtrasWindow* m_extras;
|
||||
|
||||
// If true the navigation_history isn't
|
||||
// modified if the current folder changes
|
||||
|
147
src/app/ui/layer_frame_comboboxes.cpp
Normal file
147
src/app/ui/layer_frame_comboboxes.cpp
Normal file
@ -0,0 +1,147 @@
|
||||
// Aseprite
|
||||
// Copyright (C) 2016 David Capello
|
||||
//
|
||||
// This program is distributed under the terms of
|
||||
// the End-User License Agreement for Aseprite.
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include "config.h"
|
||||
#endif
|
||||
|
||||
#include "app/ui/layer_frame_comboboxes.h"
|
||||
|
||||
#include "app/restore_visible_layers.h"
|
||||
#include "doc/frame_tag.h"
|
||||
#include "doc/layer.h"
|
||||
#include "doc/selected_frames.h"
|
||||
#include "doc/selected_layers.h"
|
||||
#include "doc/site.h"
|
||||
#include "doc/sprite.h"
|
||||
#include "ui/combobox.h"
|
||||
|
||||
namespace app {
|
||||
|
||||
const char* kAllLayers = "";
|
||||
const char* kAllFrames = "";
|
||||
const char* kSelectedLayers = "**selected-layers**";
|
||||
const char* kSelectedFrames = "**selected-frames**";
|
||||
|
||||
LayerListItem::LayerListItem(doc::Layer* layer)
|
||||
: ListItem(buildName(layer))
|
||||
, m_layer(layer)
|
||||
{
|
||||
setValue(layer->name());
|
||||
}
|
||||
|
||||
// static
|
||||
std::string LayerListItem::buildName(const doc::Layer* layer)
|
||||
{
|
||||
bool isGroup = layer->isGroup();
|
||||
std::string name;
|
||||
while (layer != layer->sprite()->root()) {
|
||||
if (!name.empty())
|
||||
name.insert(0, " > ");
|
||||
name.insert(0, layer->name());
|
||||
layer = layer->parent();
|
||||
}
|
||||
name.insert(0, isGroup ? "Group: ": "Layer: ");
|
||||
return name;
|
||||
}
|
||||
|
||||
FrameListItem::FrameListItem(doc::FrameTag* tag)
|
||||
: ListItem("Tag: " + tag->name())
|
||||
, m_tag(tag)
|
||||
{
|
||||
setValue(m_tag->name());
|
||||
}
|
||||
|
||||
void fill_layers_combobox(const doc::Sprite* sprite, ui::ComboBox* layers, const std::string& defLayer)
|
||||
{
|
||||
int i = layers->addItem("Visible layers");
|
||||
layers->getItem(i)->setValue(kAllLayers);
|
||||
|
||||
i = layers->addItem("Selected layers");
|
||||
layers->getItem(i)->setValue(kSelectedLayers);
|
||||
if (defLayer == kSelectedLayers)
|
||||
layers->setSelectedItemIndex(i);
|
||||
|
||||
doc::LayerList layersList = sprite->allLayers();
|
||||
for (auto it=layersList.rbegin(), end=layersList.rend(); it!=end; ++it) {
|
||||
doc::Layer* layer = *it;
|
||||
i = layers->addItem(new LayerListItem(layer));
|
||||
if (defLayer == layer->name())
|
||||
layers->setSelectedItemIndex(i);
|
||||
}
|
||||
}
|
||||
|
||||
void fill_frames_combobox(const doc::Sprite* sprite, ui::ComboBox* frames, const std::string& defFrame)
|
||||
{
|
||||
int i = frames->addItem("All frames");
|
||||
frames->getItem(i)->setValue(kAllFrames);
|
||||
|
||||
i = frames->addItem("Selected frames");
|
||||
frames->getItem(i)->setValue(kSelectedFrames);
|
||||
if (defFrame == kSelectedFrames)
|
||||
frames->setSelectedItemIndex(i);
|
||||
|
||||
for (auto tag : sprite->frameTags()) {
|
||||
i = frames->addItem(new FrameListItem(tag));
|
||||
if (defFrame == tag->name())
|
||||
frames->setSelectedItemIndex(i);
|
||||
}
|
||||
}
|
||||
|
||||
void calculate_visible_layers(doc::Site& site,
|
||||
const std::string& layersValue,
|
||||
RestoreVisibleLayers& layersVisibility)
|
||||
{
|
||||
if (layersValue == kSelectedLayers) {
|
||||
if (!site.selectedLayers().empty()) {
|
||||
layersVisibility.showSelectedLayers(
|
||||
site.sprite(),
|
||||
site.selectedLayers());
|
||||
}
|
||||
else {
|
||||
layersVisibility.showLayer(site.layer());
|
||||
}
|
||||
}
|
||||
else if (layersValue != kAllFrames) {
|
||||
// TODO add a getLayerByName
|
||||
for (doc::Layer* layer : site.sprite()->allLayers()) {
|
||||
if (layer->name() == layersValue) {
|
||||
layersVisibility.showLayer(layer);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
doc::FrameTag* calculate_selected_frames(const doc::Site& site,
|
||||
const std::string& framesValue,
|
||||
doc::SelectedFrames& selFrames)
|
||||
{
|
||||
doc::FrameTag* frameTag = nullptr;
|
||||
|
||||
if (framesValue == kSelectedFrames) {
|
||||
if (!site.selectedFrames().empty()) {
|
||||
selFrames = site.selectedFrames();
|
||||
}
|
||||
else {
|
||||
selFrames.insert(site.frame(), site.frame());
|
||||
}
|
||||
}
|
||||
else if (framesValue != kAllFrames) {
|
||||
frameTag = site.sprite()->frameTags().getByName(framesValue);
|
||||
if (frameTag)
|
||||
selFrames.insert(frameTag->fromFrame(),
|
||||
frameTag->toFrame());
|
||||
else
|
||||
selFrames.insert(0, site.sprite()->lastFrame());
|
||||
}
|
||||
else
|
||||
selFrames.insert(0, site.sprite()->lastFrame());
|
||||
|
||||
return frameTag;
|
||||
}
|
||||
|
||||
} // namespace app
|
67
src/app/ui/layer_frame_comboboxes.h
Normal file
67
src/app/ui/layer_frame_comboboxes.h
Normal file
@ -0,0 +1,67 @@
|
||||
// Aseprite
|
||||
// Copyright (C) 2016 David Capello
|
||||
//
|
||||
// This program is distributed under the terms of
|
||||
// the End-User License Agreement for Aseprite.
|
||||
|
||||
#ifndef APP_UI_LAYER_FRAME_COMBOBOXES_H_INCLUDED
|
||||
#define APP_UI_LAYER_FRAME_COMBOBOXES_H_INCLUDED
|
||||
#pragma once
|
||||
|
||||
#include "ui/listitem.h"
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace doc {
|
||||
class FrameTag;
|
||||
class Layer;
|
||||
class SelectedFrames;
|
||||
class SelectedLayers;
|
||||
class Site;
|
||||
class Sprite;
|
||||
}
|
||||
|
||||
namespace ui {
|
||||
class ComboBox;
|
||||
}
|
||||
|
||||
namespace app {
|
||||
|
||||
class RestoreVisibleLayers;
|
||||
|
||||
extern const char* kAllLayers;
|
||||
extern const char* kAllFrames;
|
||||
extern const char* kSelectedLayers;
|
||||
extern const char* kSelectedFrames;
|
||||
|
||||
class LayerListItem : public ui::ListItem {
|
||||
public:
|
||||
LayerListItem(doc::Layer* layer);
|
||||
doc::Layer* layer() const { return m_layer; }
|
||||
private:
|
||||
static std::string buildName(const doc::Layer* layer);
|
||||
doc::Layer* m_layer;
|
||||
};
|
||||
|
||||
class FrameListItem : public ui::ListItem {
|
||||
public:
|
||||
FrameListItem(doc::FrameTag* tag);
|
||||
doc::FrameTag* tag() const { return m_tag; }
|
||||
private:
|
||||
doc::FrameTag* m_tag;
|
||||
};
|
||||
|
||||
void fill_layers_combobox(const doc::Sprite* sprite, ui::ComboBox* layers, const std::string& defLayer);
|
||||
void fill_frames_combobox(const doc::Sprite* sprite, ui::ComboBox* frames, const std::string& defFrame);
|
||||
|
||||
void calculate_visible_layers(doc::Site& site,
|
||||
const std::string& layersValue,
|
||||
RestoreVisibleLayers& layersVisibility);
|
||||
|
||||
doc::FrameTag* calculate_selected_frames(const doc::Site& site,
|
||||
const std::string& framesValue,
|
||||
doc::SelectedFrames& selFrames);
|
||||
|
||||
} // namespace app
|
||||
|
||||
#endif
|
@ -76,6 +76,19 @@ void SelectedFrames::insert(frame_t fromFrame, frame_t toFrame)
|
||||
}
|
||||
}
|
||||
|
||||
void SelectedFrames::filter(frame_t fromFrame, frame_t toFrame)
|
||||
{
|
||||
if (fromFrame > toFrame)
|
||||
std::swap(fromFrame, toFrame);
|
||||
|
||||
// TODO improve this, avoid copying
|
||||
SelectedFrames original = *this;
|
||||
for (frame_t frame : original) {
|
||||
if (frame >= fromFrame && frame <= toFrame)
|
||||
insert(frame);
|
||||
}
|
||||
}
|
||||
|
||||
bool SelectedFrames::contains(frame_t frame) const
|
||||
{
|
||||
return std::binary_search(
|
||||
|
@ -124,6 +124,7 @@ namespace doc {
|
||||
void clear();
|
||||
void insert(frame_t frame);
|
||||
void insert(frame_t fromFrame, frame_t toFrame);
|
||||
void filter(frame_t fromFrame, frame_t toFrame);
|
||||
|
||||
bool contains(frame_t frame) const;
|
||||
|
||||
|
@ -18,6 +18,7 @@ namespace ui {
|
||||
|
||||
class Button;
|
||||
class Entry;
|
||||
class Event;
|
||||
class ListBox;
|
||||
class ListItem;
|
||||
class Window;
|
||||
|
Loading…
x
Reference in New Issue
Block a user