Allow to save specific layers/frames in Save Copy As (fix #1080)

This commit is contained in:
David Capello 2016-09-17 01:01:28 -03:00
parent 1404b0ce7d
commit 3e9e49caf9
23 changed files with 621 additions and 271 deletions

View File

@ -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" />

View File

@ -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="&amp;OK" closewindow="true" id="ok" magnet="true" width="60" />

View 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>

View File

@ -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

View File

@ -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

View File

@ -113,10 +113,19 @@ void PreviewCliDelegate::saveFile(const CliOpenFile& cof)
}
if (cof.hasFrameRange()) {
auto roi = cof.roi();
std::cout << " - Frame range from "
<< roi.fromFrame() << " to "
<< roi.toFrame() << "\n";
const auto& selFrames = cof.roi().selectedFrames();
if (!selFrames.empty()) {
if (selFrames.ranges() == 1)
std::cout << " - Frame range from "
<< 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())

View File

@ -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) {

View File

@ -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,
}
}
// Save the document
saveDocumentInBackground(context, const_cast<Document*>(document), markAsSaved);
{
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.

View File

@ -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

View File

@ -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;

View File

@ -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();

View File

@ -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.

View File

@ -155,26 +155,31 @@ 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)
static int get_time_precision(const Sprite* sprite,
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)) {
constantFrameRate = false;
break;
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;

View File

@ -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:

View File

@ -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(

View File

@ -28,6 +28,7 @@ void RestoreVisibleLayers::showLayer(Layer* layer)
{
SelectedLayers selLayers;
selLayers.insert(layer);
selLayers.propagateSelection();
showSelectedLayers(layer->sprite(), selLayers);
}

View File

@ -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

View File

@ -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

View 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

View 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

View File

@ -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(

View File

@ -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;

View File

@ -18,6 +18,7 @@ namespace ui {
class Button;
class Entry;
class Event;
class ListBox;
class ListItem;
class Window;