Simplify Export Sprite Sheet with preview + changes to the UI

These changes include an option to split layers and tags
by rows (fix #1118)
master
David Capello 2019-10-15 17:00:00 -03:00
parent 7fd1c7e0e7
commit 536a4c5d3a
18 changed files with 1203 additions and 642 deletions

View File

@ -309,6 +309,7 @@
<section id="sprite_sheet">
<option id="show_overwrite_files_alert" type="bool" default="true" />
<option id="default_extension" type="std::string" default="&quot;png&quot;" />
<option id="preview" type="bool" default="true" />
</section>
<section id="gif">
<option id="show_alert" type="bool" default="true" />
@ -447,13 +448,14 @@
<option id="rows" type="int" default="0" />
<option id="width" type="int" default="0" />
<option id="height" type="int" default="0" />
<option id="best_fit" type="bool" default="false" />
<option id="texture_filename" type="std::string" />
<option id="data_filename" type="std::string" />
<option id="data_format" type="SpriteSheetDataFormat" default="SpriteSheetDataFormat::Default" />
<option id="filename_format" type="std::string" />
<option id="border_padding" type="int" default="0" />
<option id="shape_padding" type="int" default="0" />
<option id="inner_padding" type="int" default="0" />
<option id="trim_sprite" type="bool" default="false" />
<option id="trim" type="bool" default="false" />
<option id="trim_by_grid" type="bool" default="false" />
<option id="extrude" type="bool" default="false" />
@ -461,6 +463,7 @@
<option id="layer" type="std::string" />
<option id="frame_tag" type="std::string" />
<option id="split_layers" type="bool" default="false" />
<option id="split_tags" type="bool" default="false" />
<option id="list_layers" type="bool" default="true" />
<option id="list_frame_tags" type="bool" default="true" />
<option id="list_slices" type="bool" default="true" />

View File

@ -534,25 +534,56 @@ cancel = &Cancel
[export_sprite_sheet]
title = Export Sprite Sheet
format = Format:
sheet_type = Sheet Type:
columns = # of Columns:
rows = # of Rows:
padding = Padding
border = Border:
shape = Shape:
inner = Inner:
trim = Trim
trim_by_grid = by Grid
extrude = Extrude
width = Width:
height = Height:
best_fit = Best fit for texture
layers = Layers:
split_layers = Split
split_layers_tooltip = <<<END
Generates one sprite for each layer (and for each frame).
sheet_type_tooltip = <<<END
Indicates a specific way to layout sprites in the sprite sheet:
* Horizontal: Each frame side by side.
* Vertical: Each frame one below the other.
* By Rows: Create one row for each layer or tag.
* By Columns: Create one column for each layer or tag.
* Packed: Try to fit all frames in the best possible way.
END
type_horz = Horizontal Strip
type_vert = Vertical Strip
type_rows = By Rows
type_cols = By Columns
type_pack = Packed
constraints = Constraints
constraints_tooltip = <<<END
Special constraints for the sprite sheet.
E.g. Fixed number of rows/columns or fixed
number of pixels (width/height).
END
constraint_fixed_none = None
constraint_fixed_cols = Fixed # of Columns
constraint_fixed_rows = Fixed # of Rows
constraint_fixed_width = Fixed Width
constraint_fixed_height = Fixed Height
constraint_fixed_size = Fixed Size
padding = Padding
padding_tooltip = Extra space to add between sprites
border = Border:
border_tooltip = Space between each frame and the edge of the sprite sheet
shape = Spacing:
shape_tooltip = Space between each frame in the sprite sheet
inner = Inner:
inner_tooltip = Extra space inside frame edges
trim_sprite = Trim Sprite
trim_sprite_tooltip = Trims the whole sprite before its frames are included in the sprite sheet
trim = Trim Cels
trim_tooltip = Trims each frame separately
trim_by_grid = By Grid
trim_by_grid_tooltip = Trims by grid boundaries instead of pixel by pixel
extrude = Extrude
extrude_tooltip = Adds a border to each frame duplicating the pixels of its edges
layers = Layers:
split_layers = Split Layers
split_layers_tooltip = Generates one sprite for each layer
split_tags = Split Tags
split_tags_tooltip = Generates one sprite for each tag
frames = Frames:
output = Output:
output_file = Output File
json_data = JSON Data
json_data_hash = Hash
@ -561,9 +592,18 @@ meta = Meta:
meta_layers = Layers
meta_tags = Tags
meta_slices = Slices
open_sprite_sheet = Open generated sprite sheet
data_filename_format = Item Filename:
data_filename_format_tooltip = <<<END
Each frame in the JSON data will contain a filename
(string that identifies them), this field describes
how to build each frame. You can use special marks
like {layer}, {frame}, {tag}, {tagframe}, etc.
END
preview = Preview
open_sprite_sheet = Open Generated Sprite Sheet
export = &Export
cancel = &Cancel
generating = Generating...
[file_selector]
go_back_button_tooltip = Go back one folder

View File

@ -3,51 +3,61 @@
<!-- Copyright (C) 2001-2018 David Capello -->
<gui>
<window id="export_sprite_sheet" text="@.title">
<grid columns="4">
<grid columns="4" expansive="true">
<separator horizontal="true" text="@.format" cell_hspan="4" />
<label text="@.sheet_type" />
<combobox id="sheet_type" cell_hspan="3" />
<combobox id="sheet_type" expansive="true" cell_hspan="2" cell_align="horizontal"
tooltip="@.sheet_type_tooltip" />
<check id="show_constraints" text="@.constraints"
tooltip="@.constraints_tooltip" />
<label id="columns_label" text="@.columns" />
<expr id="columns" text="" />
<boxfiller cell_hspan="2" />
<label id="rows_label" text="@.rows" />
<expr id="rows" text="" />
<boxfiller cell_hspan="2" />
<hbox />
<vbox>
<check id="padding_enabled" text="@.padding" />
<check id="trim_enabled" text="@.trim" />
<hbox id="trim_container">
<boxfiller />
<check id="grid_trim_enabled" text="@.trim_by_grid" />
</hbox>
<check id="extrude_enabled" text="@.extrude" />
</vbox>
<grid columns="2" id="padding_container" cell_hspan="2">
<label text="@.border" />
<expr id="border_padding" text="0" />
<label text="@.shape" />
<expr id="shape_padding" text="0" />
<label text="@.inner" />
<expr id="inner_padding" text="0" />
</grid>
<label id="fit_width_label" text="@.width" />
<combobox id="fit_width" text="" maxsize="5" editable="true" />
<label id="fit_height_label" text="@.height" />
<combobox id="fit_height" text="" maxsize="5" editable="true" />
<hbox id="best_fit_filler" />
<check cell_hspan="3" id="best_fit" text="@.best_fit" />
<label id="constraints_label" text="@.constraints" />
<hbox id="constraints_placeholder" cell_hspan="3">
<combobox id="constraint_type" />
<expr id="width_constraint" />
<expr id="height_constraint" />
</hbox>
<label text="@.layers" />
<combobox id="layers" text="" cell_hspan="2" />
<check id="split_layers" text="@.split_layers" tooltip="@.split_layers_tooltip" />
<label text="@.frames" />
<combobox id="frames" text="" cell_hspan="3" />
<combobox id="frames" text="" cell_hspan="2" />
<check id="split_tags" text="@.split_tags" tooltip="@.split_tags_tooltip" />
<hbox />
<hbox cell_hspan="3">
<vbox>
<check id="trim_sprite_enabled" text="@.trim_sprite" tooltip="@.trim_sprite_tooltip" />
<check id="trim_enabled" text="@.trim" tooltip="@.trim_tooltip" />
<vbox id="trim_container" cell_hspan="2">
<check id="grid_trim_enabled"
text="@.trim_by_grid"
tooltip="@.trim_by_grid_tooltip" />
</vbox>
</vbox>
<separator vertical="true" />
<vbox>
<check id="padding_enabled" text="@.padding" tooltip="@.padding_tooltip" />
<check id="extrude_enabled"
text="@.extrude"
tooltip="@.extrude_tooltip" />
</vbox>
<vbox>
<grid columns="2" id="padding_container">
<label text="@.border" />
<expr id="border_padding" text="0" tooltip="@.border_tooltip" />
<label text="@.shape" />
<expr id="shape_padding" text="0" tooltip="@.shape_tooltip" />
<label text="@.inner" />
<expr id="inner_padding" text="0" tooltip="@.inner_tooltip" />
</grid>
</vbox>
</hbox>
<separator horizontal="true" text="@.output" cell_hspan="4" cell_align="horizontal" />
<check id="image_enabled" text="@.output_file" />
<button id="image_filename" cell_hspan="3" />
@ -56,7 +66,7 @@
<button id="data_filename" cell_hspan="3" />
<hbox />
<hbox id="data_meta" cell_hspan="3">
<hbox id="data_meta" cell_hspan="3" cell_align="horizontal">
<combobox id="data_format">
<listitem text="@.json_data_hash" value="0" />
<listitem text="@.json_data_array" value="1" />
@ -67,9 +77,21 @@
<check id="list_slices" text="@.meta_slices" />
</hbox>
<check id="open_generated" text="@.open_sprite_sheet" cell_hspan="4" />
<hbox />
<hbox id="data_filename_format_placeholder" cell_hspan="3" cell_align="horizontal">
<label text="@.data_filename_format" />
<entry id="data_filename_format" maxsize="1024" maxwidth="256" expansive="true"
tooltip="@.data_filename_format_tooltip" />
<link text="(?)" url="https://www.aseprite.org/docs/cli/#filename-format" />
</hbox>
<separator horizontal="true" cell_hspan="4" />
<hbox cell_hspan="4">
<hbox>
<check id="open_generated" text="@.open_sprite_sheet" cell_hspan="3" />
<check id="preview" text="@.preview" cell_hspan="1" />
</hbox>
<boxfiller />
<hbox homogeneous="true">
<button text="@.export" minwidth="60" id="export_button" magnet="true" />

2
laf

@ -1 +1 @@
Subproject commit 8cacc60740b76a8012aee3e35a6681516d4f871d
Subproject commit 5edb4d5441571eddc2b5f378fc35e6be78048541

View File

@ -55,6 +55,7 @@ AppOptions::AppOptions(int argc, const char* argv[])
, m_tag(m_po.add("tag").alias("frame-tag").requiresValue("<name>").description("Include tagged frames in the sheet"))
, m_frameRange(m_po.add("frame-range").requiresValue("from,to").description("Only export frames in the [from,to] range"))
, m_ignoreEmpty(m_po.add("ignore-empty").description("Do not export empty frames/cels"))
, m_mergeDuplicates(m_po.add("merge-duplicates").description("Merge all duplicate frames into one in the sprite sheet"))
, m_borderPadding(m_po.add("border-padding").requiresValue("<value>").description("Add padding on the texture borders"))
, m_shapePadding(m_po.add("shape-padding").requiresValue("<value>").description("Add padding between frames"))
, m_innerPadding(m_po.add("inner-padding").requiresValue("<value>").description("Add padding inside each frame"))

View File

@ -69,6 +69,7 @@ public:
const Option& tag() const { return m_tag; }
const Option& frameRange() const { return m_frameRange; }
const Option& ignoreEmpty() const { return m_ignoreEmpty; }
const Option& mergeDuplicates() const { return m_mergeDuplicates; }
const Option& borderPadding() const { return m_borderPadding; }
const Option& shapePadding() const { return m_shapePadding; }
const Option& innerPadding() const { return m_innerPadding; }
@ -129,6 +130,7 @@ private:
Option& m_tag;
Option& m_frameRange;
Option& m_ignoreEmpty;
Option& m_mergeDuplicates;
Option& m_borderPadding;
Option& m_shapePadding;
Option& m_innerPadding;

View File

@ -305,6 +305,11 @@ void CliProcessor::process(Context* ctx)
if (m_exporter)
m_exporter->setIgnoreEmptyCels(true);
}
// --merge-duplicates
else if (opt == &m_options.mergeDuplicates()) {
if (m_exporter)
m_exporter->setMergeDuplicates(true);
}
// --border-padding
else if (opt == &m_options.borderPadding()) {
if (m_exporter)
@ -625,37 +630,16 @@ bool CliProcessor::openFile(Context* ctx, CliOpenFile& cof)
}
}
if (cof.hasLayersFilter()) {
SelectedLayers filteredLayers;
SelectedLayers filteredLayers;
if (cof.hasLayersFilter())
filterLayers(doc->sprite(), cof, filteredLayers);
if (cof.splitLayers) {
for (Layer* layer : filteredLayers.toAllLayersList()) {
SelectedLayers oneLayer;
oneLayer.insert(layer);
m_exporter->addDocument(doc, tag, &oneLayer,
(!selFrames.empty() ? &selFrames: nullptr));
}
}
else {
m_exporter->addDocument(doc, tag, &filteredLayers,
(!selFrames.empty() ? &selFrames: nullptr));
}
}
else if (cof.splitLayers) {
for (auto layer : doc->sprite()->allVisibleLayers()) {
SelectedLayers oneLayer;
oneLayer.insert(layer);
m_exporter->addDocument(doc, tag, &oneLayer,
(!selFrames.empty() ? &selFrames: nullptr));
}
}
else {
m_exporter->addDocument(doc, tag, nullptr,
(!selFrames.empty() ? &selFrames: nullptr));
}
m_exporter->addDocumentSamples(
doc, tag,
cof.splitLayers,
cof.splitTags,
(cof.hasLayersFilter() ? &filteredLayers: nullptr),
(!selFrames.empty() ? &selFrames: nullptr));
}
}

View File

@ -121,7 +121,9 @@ void DefaultCliDelegate::exportFiles(Context* ctx, DocExporter& exporter)
{
LOG("APP: Exporting sheet...\n");
std::unique_ptr<Doc> spriteSheet(exporter.exportSheet(ctx));
base::task_token token;
std::unique_ptr<Doc> spriteSheet(
exporter.exportSheet(ctx, token));
// Sprite sheet isn't used, we just delete it.

File diff suppressed because it is too large Load Diff

View File

@ -17,7 +17,6 @@
#include "app/doc.h"
#include "app/file/file.h"
#include "app/filename_formatter.h"
#include "app/pref/preferences.h"
#include "app/restore_visible_layers.h"
#include "app/snap_to_grid.h"
#include "doc/images_map.h"
@ -119,9 +118,9 @@ private:
typedef std::shared_ptr<SampleBounds> SampleBoundsPtr;
DocExporter::Item::Item(Doc* doc,
doc::Tag* tag,
doc::SelectedLayers* selLayers,
doc::SelectedFrames* selFrames)
const doc::Tag* tag,
const doc::SelectedLayers* selLayers,
const doc::SelectedFrames* selFrames)
: doc(doc)
, tag(tag)
, selLayers(selLayers ? new doc::SelectedLayers(*selLayers): nullptr)
@ -176,11 +175,16 @@ doc::SelectedFrames DocExporter::Item::getSelectedFrames() const
class DocExporter::Sample {
public:
Sample(Doc* document, Sprite* sprite, SelectedLayers* selLayers,
frame_t frame, const std::string& filename, int innerPadding, bool extrude) :
frame_t frame,
const Tag* tag,
const std::string& filename,
const int innerPadding,
const bool extrude) :
m_document(document),
m_sprite(sprite),
m_selLayers(selLayers),
m_frame(frame),
m_tag(tag),
m_filename(filename),
m_innerPadding(innerPadding),
m_extrude(extrude),
@ -195,6 +199,7 @@ public:
return (m_selLayers && m_selLayers->size() == 1 ? *m_selLayers->begin():
nullptr);
}
const Tag* tag() const { return m_tag; }
SelectedLayers* selectedLayers() const { return m_selLayers; }
frame_t frame() const { return m_frame; }
std::string filename() const { return m_filename; }
@ -257,7 +262,10 @@ public:
*m_selLayers);
render::Render render;
render.setNewBlend(Preferences::instance().experimental.newBlend());
// 1) We cannot use the Preferences because this is called from a non-UI thread
// 2) We should use the new blend mode always when we're saving files
//render.setNewBlend(Preferences::instance().experimental.newBlend());
if (extrude) {
const gfx::Rect& trim = trimmedBounds();
@ -296,6 +304,7 @@ private:
Sprite* m_sprite;
SelectedLayers* m_selLayers;
frame_t m_frame;
const Tag* m_tag;
std::string m_filename;
int m_innerPadding;
bool m_extrude;
@ -311,6 +320,7 @@ public:
typedef List::const_iterator const_iterator;
bool empty() const { return m_samples.empty(); }
int size() const { return int(m_samples.size()); }
void addSample(const Sample& sample) {
m_samples.push_back(sample);
@ -332,45 +342,88 @@ private:
class DocExporter::LayoutSamples {
public:
virtual ~LayoutSamples() { }
virtual void layoutSamples(Samples& samples, int borderPadding, int shapePadding, int& width, int& height) = 0;
virtual void layoutSamples(Samples& samples,
int borderPadding,
int shapePadding,
int& width, int& height,
base::task_token& token) = 0;
};
class DocExporter::SimpleLayoutSamples : public DocExporter::LayoutSamples {
public:
SimpleLayoutSamples(SpriteSheetType type)
: m_type(type) {
SimpleLayoutSamples(SpriteSheetType type,
int maxCols, int maxRows)
: m_type(type)
, m_maxCols(maxCols)
, m_maxRows(maxRows) {
}
void layoutSamples(Samples& samples, int borderPadding, int shapePadding, int& width, int& height) override {
const Sprite* oldSprite = NULL;
const Layer* oldLayer = NULL;
void layoutSamples(Samples& samples,
int borderPadding,
int shapePadding,
int& width, int& height,
base::task_token& token) override {
DX_TRACE("SimpleLayoutSamples type", (int)m_type, width, height);
const bool breakBands =
(m_type == SpriteSheetType::Columns ||
m_type == SpriteSheetType::Rows);
const Sprite* oldSprite = nullptr;
const Layer* oldLayer = nullptr;
const Tag* oldTag = nullptr;
gfx::Point framePt(borderPadding, borderPadding);
gfx::Size rowSize(0, 0);
int i = 0;
int itemInBand = 0;
int itemsPerBand = -1;
if (breakBands) {
if (m_type == SpriteSheetType::Columns && m_maxRows > 0)
itemsPerBand = m_maxRows;
if (m_type == SpriteSheetType::Rows && m_maxCols > 0)
itemsPerBand = m_maxCols;
}
for (auto& sample : samples) {
if (sample.isLinked())
if (token.canceled())
return;
token.set_progress(0.2f + 0.2f * i / samples.size());
if (sample.isLinked()) {
++i;
continue;
}
if (sample.isEmpty()) {
sample.setInTextureBounds(gfx::Rect(0, 0, 0, 0));
++i;
continue;
}
const Sprite* sprite = sample.sprite();
const Layer* layer = sample.layer();
const Tag* tag = sample.tag();
gfx::Size size = sample.requiredSize();
if (oldSprite) {
if (breakBands && oldSprite) {
const bool nextBand =
(oldSprite != sprite ||
oldLayer != layer ||
oldTag != tag ||
itemInBand == itemsPerBand);
if (m_type == SpriteSheetType::Columns) {
// If the user didn't specify a height for the texture, we
// put each sprite/layer in a different column.
if (height == 0) {
// New sprite or layer, go to next column.
if (oldSprite != sprite || oldLayer != layer) {
if (nextBand) {
framePt.x += rowSize.w + shapePadding;
framePt.y = borderPadding;
rowSize = size;
itemInBand = 0;
}
}
// When a texture height is specified, we can put different
@ -382,15 +435,16 @@ public:
rowSize = size;
}
}
else {
else if (m_type == SpriteSheetType::Rows) {
// If the user didn't specify a width for the texture, we put
// each sprite/layer in a different row.
if (width == 0) {
// New sprite or layer, go to next row.
if (oldSprite != sprite || oldLayer != layer) {
if (nextBand) {
framePt.x = borderPadding;
framePt.y += rowSize.h + shapePadding;
rowSize = size;
itemInBand = 0;
}
}
// When a texture width is specified, we can put different
@ -402,15 +456,20 @@ public:
rowSize = size;
}
}
else {
ASSERT(false);
}
}
sample.setInTextureBounds(gfx::Rect(framePt, size));
// Next frame position.
if (m_type == SpriteSheetType::Columns) {
if (m_type == SpriteSheetType::Vertical ||
m_type == SpriteSheetType::Columns) {
framePt.y += size.h + shapePadding;
}
else {
else if (m_type == SpriteSheetType::Horizontal ||
m_type == SpriteSheetType::Rows) {
framePt.x += size.w + shapePadding;
}
@ -418,32 +477,47 @@ public:
oldSprite = sprite;
oldLayer = layer;
oldTag = tag;
++itemInBand;
++i;
}
DX_TRACE("-> SimpleLayoutSamples", width, height);
}
private:
SpriteSheetType m_type;
int m_maxCols;
int m_maxRows;
};
class DocExporter::BestFitLayoutSamples : public DocExporter::LayoutSamples {
ImageBufferPtr m_imageBuf;
public:
BestFitLayoutSamples(ImageBufferPtr& buf)
: m_imageBuf(buf) {
}
void layoutSamples(Samples& samples, int borderPadding, int shapePadding, int& width, int& height) override {
void layoutSamples(Samples& samples,
int borderPadding,
int shapePadding,
int& width, int& height,
base::task_token& token) override {
gfx::PackingRects pr(borderPadding, shapePadding);
doc::ImagesMap duplicates;
uint32_t i = 0;
for (auto& sample : samples) {
if (token.canceled())
return;
token.set_progress_range(0.2f, 0.3f);
token.set_progress(float(i) / samples.size());
if (sample.isLinked() ||
sample.isEmpty()) {
++i;
continue;
}
ImageRef sampleRender(sample.createRender(m_imageBuf));
// We have to use one ImageBuffer for each image because we're
// going to store all images in the "duplicates" map.
doc::ImageBufferPtr sampleBuf = std::make_shared<doc::ImageBuffer>();
doc::ImageRef sampleRender(sample.createRender(sampleBuf));
auto it = duplicates.find(sampleRender);
if (it != duplicates.end()) {
const uint32_t j = it->second;
@ -458,13 +532,16 @@ public:
++i;
}
token.set_progress_range(0.3f, 0.4f);
if (width == 0 || height == 0) {
gfx::Size sz = pr.bestFit();
gfx::Size sz = pr.bestFit(token, width, height);
width = sz.w;
height = sz.h;
}
else
pr.pack(gfx::Size(width, height));
else {
pr.pack(gfx::Size(width, height), token);
}
token.set_progress_range(0.0f, 1.0f);
auto it = pr.begin();
for (auto& sample : samples) {
@ -480,24 +557,45 @@ public:
};
DocExporter::DocExporter()
: m_dataFormat(SpriteSheetDataFormat::Default)
, m_textureWidth(0)
, m_textureHeight(0)
, m_sheetType(SpriteSheetType::None)
, m_ignoreEmptyCels(false)
, m_borderPadding(0)
, m_shapePadding(0)
, m_innerPadding(0)
, m_trimCels(false)
, m_trimByGrid(false)
, m_extrude(false)
, m_listTags(false)
, m_listLayers(false)
, m_listSlices(false)
: m_docBuf(std::make_shared<doc::ImageBuffer>())
, m_sampleBuf(std::make_shared<doc::ImageBuffer>())
{
reset();
}
Doc* DocExporter::exportSheet(Context* ctx)
void DocExporter::reset()
{
m_sheetType = SpriteSheetType::None;
m_dataFormat = SpriteSheetDataFormat::Default;
m_dataFilename.clear();
m_textureFilename.clear();
m_filenameFormat.clear();
m_textureWidth = 0;
m_textureHeight = 0;
m_textureColumns = 0;
m_textureRows = 0;
m_borderPadding = 0;
m_shapePadding = 0;
m_innerPadding = 0;
m_ignoreEmptyCels = false;
m_mergeDuplicates = false;
m_trimSprite = false;
m_trimCels = false;
m_trimByGrid = false;
m_extrude = false;
m_listTags = false;
m_listLayers = false;
m_listSlices = false;
m_documents.clear();
m_tagDelta.clear();
}
void DocExporter::setDocImageBuffer(const doc::ImageBufferPtr& docBuf)
{
m_docBuf = docBuf;
}
Doc* DocExporter::exportSheet(Context* ctx, base::task_token& token)
{
// We output the metadata to std::cout if the user didn't specify a file.
std::ofstream fos;
@ -530,59 +628,191 @@ Doc* DocExporter::exportSheet(Context* ctx)
// Steps for sheet construction:
// 1) Capture the samples (each sprite+frame pair)
Samples samples;
captureSamples(samples);
captureSamples(samples, token);
if (samples.empty()) {
Console console;
console.printf("No documents to export");
return nullptr;
}
if (token.canceled())
return nullptr;
token.set_progress(0.2f);
// 2) Layout those samples in a texture field.
layoutSamples(samples);
layoutSamples(samples, token);
if (token.canceled())
return nullptr;
token.set_progress(0.4f);
// 3) Create and render the texture.
std::unique_ptr<Doc> textureDocument(
createEmptyTexture(samples));
createEmptyTexture(samples, token));
if (token.canceled())
return nullptr;
token.set_progress(0.6f);
Sprite* texture = textureDocument->sprite();
Image* textureImage = texture->root()->firstLayer()
->cel(frame_t(0))->image();
renderTexture(ctx, samples, textureImage);
renderTexture(ctx, samples, textureImage, token);
if (token.canceled())
return nullptr;
token.set_progress(0.8f);
// Trim texture
if (m_trimSprite || m_trimCels)
trimTexture(samples, texture);
token.set_progress(0.9f);
// Save the metadata.
if (osbuf)
createDataFile(samples, os, textureImage);
createDataFile(samples, os, texture);
token.set_progress(0.95f);
// Save the image files.
if (!m_textureFilename.empty()) {
DX_TRACE("DocExporter::exportSheet", m_textureFilename);
textureDocument->setFilename(m_textureFilename.c_str());
int ret = save_document(ctx, textureDocument.get());
if (ret == 0)
textureDocument->markAsSaved();
}
token.set_progress(1.0f);
return textureDocument.release();
}
gfx::Size DocExporter::calculateSheetSize()
{
base::task_token token;
Samples samples;
captureSamples(samples);
layoutSamples(samples);
return calculateSheetSize(samples);
captureSamples(samples, token);
layoutSamples(samples, token);
return calculateSheetSize(samples, token);
}
void DocExporter::captureSamples(Samples& samples)
void DocExporter::addDocument(
Doc* doc,
const doc::Tag* tag,
const doc::SelectedLayers* selLayers,
const doc::SelectedFrames* selFrames)
{
DX_TRACE("DocExporter::addDocument doc=", doc, "tag=", tag);
m_documents.push_back(Item(doc, tag, selLayers, selFrames));
}
int DocExporter::addDocumentSamples(
Doc* doc,
const doc::Tag* thisTag,
const bool splitLayers,
const bool splitTags,
const doc::SelectedLayers* selLayers,
const doc::SelectedFrames* selFrames)
{
DX_TRACE("DocExporter::addDocumentSamples");
std::vector<const Tag*> tags;
if (thisTag)
tags.push_back(thisTag);
else if (splitTags) {
if (selFrames) {
const Tag* oldTag = nullptr;
for (frame_t frame : *selFrames) {
const Tag* tag = doc->sprite()->tags().innerTag(frame);
if (oldTag != tag) {
oldTag = tag;
tags.push_back(tag);
}
}
}
else {
for (const Tag* tag : doc->sprite()->tags())
tags.push_back(tag);
}
if (tags.empty())
tags.push_back(nullptr);
}
else {
tags.push_back(nullptr);
}
doc::SelectedFrames selFramesTmp;
int items = 0;
for (const Tag* tag : tags) {
const doc::SelectedFrames* thisSelFrames = nullptr;
if (selFrames) {
if (tag) {
selFramesTmp.clear();
for (frame_t frame=tag->fromFrame(); frame<=tag->toFrame(); ++frame) {
if (selFrames->contains(frame))
selFramesTmp.insert(frame);
}
thisSelFrames = &selFramesTmp;
}
else {
selFramesTmp = *selFrames;
thisSelFrames = &selFramesTmp;
}
}
else if (tag) {
ASSERT(tag);
selFramesTmp.clear();
selFramesTmp.insert(tag->fromFrame(),
tag->toFrame());
thisSelFrames = &selFramesTmp;
}
if (splitLayers) {
if (selLayers) {
for (auto layer : selLayers->toAllLayersList()) {
if (layer->isGroup()) // Ignore groups
continue;
SelectedLayers oneLayer;
oneLayer.insert(layer);
addDocument(doc, tag, &oneLayer, thisSelFrames);
++items;
}
}
else {
for (auto layer : doc->sprite()->allVisibleLayers()) {
if (layer->isGroup()) // Ignore groups
continue;
SelectedLayers oneLayer;
oneLayer.insert(layer);
addDocument(doc, tag, &oneLayer, thisSelFrames);
++items;
}
}
}
else {
addDocument(doc, tag, selLayers, thisSelFrames);
++items;
}
}
return std::max(1, items);
}
void DocExporter::captureSamples(Samples& samples,
base::task_token& token)
{
DX_TRACE("DX: Capture samples");
for (auto& item : m_documents) {
if (token.canceled())
return;
Doc* doc = item.doc;
Sprite* sprite = doc->sprite();
Layer* layer = (item.selLayers && item.selLayers->size() == 1 ?
*item.selLayers->begin(): nullptr);
Tag* tag = item.tag;
const Tag* tag = item.tag;
int frames = item.frames();
DX_TRACE("DX: - Item:", doc->filename(),
@ -601,8 +831,11 @@ void DocExporter::captureSamples(Samples& samples)
frame_t outputFrame = 0;
for (frame_t frame : item.getSelectedFrames()) {
Tag* innerTag = (tag ? tag: sprite->tags().innerTag(frame));
Tag* outerTag = sprite->tags().outerTag(frame);
if (token.canceled())
return;
const Tag* innerTag = (tag ? tag: sprite->tags().innerTag(frame));
const Tag* outerTag = sprite->tags().outerTag(frame);
FilenameInfo fnInfo;
fnInfo
.filename(doc->filename())
@ -617,7 +850,10 @@ void DocExporter::captureSamples(Samples& samples)
std::string filename = filename_formatter(format, fnInfo);
Sample sample(doc, sprite, item.selLayers, frame, filename, m_innerPadding, m_extrude);
Sample sample(
doc, sprite, item.selLayers, frame,
(innerTag && is_tag_in_filename_format(format) ? innerTag: nullptr),
filename, m_innerPadding, m_extrude);
Cel* cel = nullptr;
Cel* link = nullptr;
bool done = false;
@ -629,8 +865,11 @@ void DocExporter::captureSamples(Samples& samples)
}
// Re-use linked samples
if (link) {
if (link && mergeDuplicates()) {
for (const Sample& other : samples) {
if (token.canceled())
return;
if (other.sprite() == sprite &&
other.layer() == layer &&
other.frame() == link->frame()) {
@ -652,7 +891,7 @@ void DocExporter::captureSamples(Samples& samples)
if (layer && layer->isImage() && !cel && m_ignoreEmptyCels)
continue;
ImageRef sampleRender(sample.createRender(m_sampleRenderBuf));
ImageRef sampleRender(sample.createRender(m_sampleBuf));
gfx::Rect frameBounds;
doc::color_t refColor = 0;
@ -724,31 +963,43 @@ void DocExporter::captureSamples(Samples& samples)
}
}
void DocExporter::layoutSamples(Samples& samples)
void DocExporter::layoutSamples(Samples& samples,
base::task_token& token)
{
int width = m_textureWidth;
int height = m_textureHeight;
switch (m_sheetType) {
case SpriteSheetType::Packed: {
BestFitLayoutSamples layout(m_sampleRenderBuf);
BestFitLayoutSamples layout;
layout.layoutSamples(
samples, m_borderPadding, m_shapePadding,
m_textureWidth, m_textureHeight);
width, height, token);
break;
}
default: {
SimpleLayoutSamples layout(m_sheetType);
SimpleLayoutSamples layout(
m_sheetType, m_textureColumns, m_textureRows);
layout.layoutSamples(
samples, m_borderPadding, m_shapePadding,
m_textureWidth, m_textureHeight);
width, height, token);
break;
}
}
}
gfx::Size DocExporter::calculateSheetSize(const Samples& samples) const
gfx::Size DocExporter::calculateSheetSize(const Samples& samples,
base::task_token& token) const
{
DX_TRACE("DX: calculateSheetSize predefined texture size",
m_textureWidth, m_textureHeight);
gfx::Rect fullTextureBounds(0, 0, m_textureWidth, m_textureHeight);
for (const auto& sample : samples) {
if (token.canceled())
return gfx::Size(0, 0);
if (sample.isLinked() ||
sample.isDuplicated() ||
sample.isEmpty())
@ -772,11 +1023,16 @@ gfx::Size DocExporter::calculateSheetSize(const Samples& samples) const
if (m_textureWidth == 0) fullTextureBounds.w += m_borderPadding;
if (m_textureHeight == 0) fullTextureBounds.h += m_borderPadding;
DX_TRACE("DX: calculateSheetSize -> ",
fullTextureBounds.x+fullTextureBounds.w,
fullTextureBounds.y+fullTextureBounds.h);
return gfx::Size(fullTextureBounds.x+fullTextureBounds.w,
fullTextureBounds.y+fullTextureBounds.h);
}
Doc* DocExporter::createEmptyTexture(const Samples& samples) const
Doc* DocExporter::createEmptyTexture(const Samples& samples,
base::task_token& token) const
{
ColorMode colorMode = ColorMode::INDEXED;
Palette* palette = nullptr;
@ -785,6 +1041,9 @@ Doc* DocExporter::createEmptyTexture(const Samples& samples) const
color_t transparentColor = 0;
for (const auto& sample : samples) {
if (token.canceled())
return nullptr;
if (sample.isLinked() ||
sample.isDuplicated() ||
sample.isEmpty())
@ -818,18 +1077,22 @@ Doc* DocExporter::createEmptyTexture(const Samples& samples) const
}
}
gfx::Size textureSize = calculateSheetSize(samples);
gfx::Size textureSize = calculateSheetSize(samples, token);
if (token.canceled())
return nullptr;
std::unique_ptr<Sprite> sprite(
Sprite::MakeStdSprite(
// TODO calculate a proper transparent color for the sprite sheet
ImageSpec(colorMode, textureSize.w, textureSize.h, 0,
ImageSpec(colorMode,
std::max(textureSize.w, m_textureWidth),
std::max(textureSize.h, m_textureHeight),
transparentColor,
(colorSpace ? colorSpace: gfx::ColorSpace::MakeNone())),
maxColors));
maxColors,
m_docBuf));
if (palette)
sprite->setPalette(palette, false);
sprite->setTransparentColor(transparentColor);
std::unique_ptr<Doc> document(new Doc(sprite.get()));
sprite.release();
@ -837,15 +1100,25 @@ Doc* DocExporter::createEmptyTexture(const Samples& samples) const
return document.release();
}
void DocExporter::renderTexture(Context* ctx, const Samples& samples, Image* textureImage) const
void DocExporter::renderTexture(Context* ctx,
const Samples& samples,
Image* textureImage,
base::task_token& token) const
{
textureImage->clear(0);
int i = 0;
for (const auto& sample : samples) {
if (token.canceled())
return;
token.set_progress(0.6f + 0.2f * i / int(samples.size()));
if (sample.isLinked() ||
sample.isDuplicated() ||
sample.isEmpty())
sample.isEmpty()) {
++i;
continue;
}
// Make the sprite compatible with the texture so the render()
// works correctly.
@ -863,10 +1136,44 @@ void DocExporter::renderTexture(Context* ctx, const Samples& samples, Image* tex
sample.inTextureBounds().x+m_innerPadding,
sample.inTextureBounds().y+m_innerPadding,
m_extrude);
++i;
}
}
void DocExporter::createDataFile(const Samples& samples, std::ostream& os, Image* textureImage)
void DocExporter::trimTexture(const Samples& samples,
doc::Sprite* texture) const
{
if (m_textureWidth > 0 && m_textureHeight > 0)
return;
gfx::Size size = texture->size();
gfx::Rect bounds(0, 0, 1, 1);
for (const auto& sample : samples) {
if (sample.isLinked() ||
sample.isDuplicated() ||
sample.isEmpty())
continue;
bounds |= sample.inTextureBounds();
}
if (m_textureWidth == 0) {
ASSERT(size.w >= bounds.w);
size.w = bounds.w;
}
if (m_textureHeight == 0) {
ASSERT(size.h >= bounds.h);
size.h = bounds.h;
}
texture->setSize(m_textureWidth > 0 ? m_textureWidth: size.w,
m_textureHeight > 0 ? m_textureHeight: size.h);
}
void DocExporter::createDataFile(const Samples& samples,
std::ostream& os,
doc::Sprite* texture)
{
std::string frames_begin;
std::string frames_end;
@ -950,10 +1257,10 @@ void DocExporter::createDataFile(const Samples& samples, std::ostream& os, Image
<< escape_for_json(base::get_file_name(m_textureFilename)).c_str()
<< "\",\n";
os << " \"format\": \"" << (textureImage->pixelFormat() == IMAGE_RGB ? "RGBA8888": "I8") << "\",\n"
os << " \"format\": \"" << (texture->pixelFormat() == IMAGE_RGB ? "RGBA8888": "I8") << "\",\n"
<< " \"size\": { "
<< "\"w\": " << textureImage->width() << ", "
<< "\"h\": " << textureImage->height() << " },\n"
<< "\"w\": " << texture->width() << ", "
<< "\"h\": " << texture->height() << " },\n"
<< " \"scale\": \"1\"";
// meta.frameTags

View File

@ -12,6 +12,7 @@
#include "app/sprite_sheet_data_format.h"
#include "app/sprite_sheet_type.h"
#include "base/disable_copying.h"
#include "base/task.h"
#include "doc/frame.h"
#include "doc/image_buffer.h"
#include "doc/object_id.h"
@ -27,6 +28,7 @@ namespace doc {
class Image;
class SelectedFrames;
class SelectedLayers;
class Sprite;
class Tag;
}
@ -39,33 +41,41 @@ namespace app {
public:
DocExporter();
void reset();
void setDocImageBuffer(const doc::ImageBufferPtr& docBuf);
SpriteSheetDataFormat dataFormat() const { return m_dataFormat; }
const std::string& dataFilename() { return m_dataFilename; }
const std::string& textureFilename() { return m_textureFilename; }
int textureWidth() const { return m_textureWidth; }
int textureHeight() const { return m_textureHeight; }
// int textureWidth() const { return m_textureWidth; }
// int textureHeight() const { return m_textureHeight; }
SpriteSheetType spriteSheetType() { return m_sheetType; }
bool ignoreEmptyCels() { return m_ignoreEmptyCels; }
int borderPadding() const { return m_borderPadding; }
int shapePadding() const { return m_shapePadding; }
int innerPadding() const { return m_innerPadding; }
bool trimCels() const { return m_trimCels; }
bool trimByGrid() const { return m_trimByGrid; }
bool extrude() const { return m_extrude; }
// bool ignoreEmptyCels() { return m_ignoreEmptyCels; }
// int borderPadding() const { return m_borderPadding; }
// int shapePadding() const { return m_shapePadding; }
// int innerPadding() const { return m_innerPadding; }
// bool trimSprite() const { return m_trimSprite; }
// bool trimCels() const { return m_trimCels; }
// bool trimByGrid() const { return m_trimByGrid; }
// bool extrude() const { return m_extrude; }
const std::string& filenameFormat() const { return m_filenameFormat; }
bool listTags() const { return m_listTags; }
bool listLayers() const { return m_listLayers; }
// bool listTags() const { return m_listTags; }
// bool listLayers() const { return m_listLayers; }
void setDataFormat(SpriteSheetDataFormat format) { m_dataFormat = format; }
void setDataFilename(const std::string& filename) { m_dataFilename = filename; }
void setTextureFilename(const std::string& filename) { m_textureFilename = filename; }
void setTextureWidth(int width) { m_textureWidth = width; }
void setTextureHeight(int height) { m_textureHeight = height; }
void setTextureColumns(int columns) { m_textureColumns = columns; }
void setTextureRows(int rows) { m_textureRows = rows; }
void setSpriteSheetType(SpriteSheetType type) { m_sheetType = type; }
void setIgnoreEmptyCels(bool ignore) { m_ignoreEmptyCels = ignore; }
void setMergeDuplicates(bool merge) { m_mergeDuplicates = merge; }
void setBorderPadding(int padding) { m_borderPadding = padding; }
void setShapePadding(int padding) { m_shapePadding = padding; }
void setInnerPadding(int padding) { m_innerPadding = padding; }
void setTrimSprite(bool trim) { m_trimSprite = trim; }
void setTrimCels(bool trim) { m_trimCels = trim; }
void setTrimByGrid(bool trimByGrid) { m_trimByGrid = trimByGrid; }
void setExtrude(bool extrude) { m_extrude = extrude; }
@ -74,14 +84,21 @@ namespace app {
void setListLayers(bool value) { m_listLayers = value; }
void setListSlices(bool value) { m_listSlices = value; }
void addDocument(Doc* document,
doc::Tag* tag,
doc::SelectedLayers* selLayers,
doc::SelectedFrames* selFrames) {
m_documents.push_back(Item(document, tag, selLayers, selFrames));
}
void addDocument(
Doc* doc,
const doc::Tag* tag,
const doc::SelectedLayers* selLayers,
const doc::SelectedFrames* selFrames);
Doc* exportSheet(Context* ctx);
int addDocumentSamples(
Doc* doc,
const doc::Tag* tag,
const bool splitLayers,
const bool splitTags,
const doc::SelectedLayers* selLayers,
const doc::SelectedFrames* selFrames);
Doc* exportSheet(Context* ctx, base::task_token& token);
gfx::Size calculateSheetSize();
private:
@ -91,24 +108,33 @@ namespace app {
class SimpleLayoutSamples;
class BestFitLayoutSamples;
void captureSamples(Samples& samples);
void layoutSamples(Samples& samples);
gfx::Size calculateSheetSize(const Samples& samples) const;
Doc* createEmptyTexture(const Samples& samples) const;
void renderTexture(Context* ctx, const Samples& samples, doc::Image* textureImage) const;
void createDataFile(const Samples& samples, std::ostream& os, doc::Image* textureImage);
bool mergeDuplicates() const { return m_mergeDuplicates; }
void captureSamples(Samples& samples,
base::task_token& token);
void layoutSamples(Samples& samples,
base::task_token& token);
gfx::Size calculateSheetSize(const Samples& samples,
base::task_token& token) const;
Doc* createEmptyTexture(const Samples& samples,
base::task_token& token) const;
void renderTexture(Context* ctx,
const Samples& samples,
doc::Image* textureImage,
base::task_token& token) const;
void trimTexture(const Samples& samples, doc::Sprite* texture) const;
void createDataFile(const Samples& samples, std::ostream& os, doc::Sprite* texture);
class Item {
public:
Doc* doc;
doc::Tag* tag;
const doc::Tag* tag;
doc::SelectedLayers* selLayers;
doc::SelectedFrames* selFrames;
Item(Doc* doc,
doc::Tag* tag,
doc::SelectedLayers* selLayers,
doc::SelectedFrames* selFrames);
const doc::Tag* tag,
const doc::SelectedLayers* selLayers,
const doc::SelectedFrames* selFrames);
Item(Item&& other);
~Item();
@ -121,31 +147,38 @@ namespace app {
};
typedef std::vector<Item> Items;
SpriteSheetType m_sheetType;
SpriteSheetDataFormat m_dataFormat;
std::string m_dataFilename;
std::string m_textureFilename;
std::string m_filenameFormat;
int m_textureWidth;
int m_textureHeight;
SpriteSheetType m_sheetType;
bool m_ignoreEmptyCels;
int m_textureColumns;
int m_textureRows;
int m_borderPadding;
int m_shapePadding;
int m_innerPadding;
bool m_ignoreEmptyCels;
bool m_mergeDuplicates;
bool m_trimSprite;
bool m_trimCels;
bool m_trimByGrid;
bool m_extrude;
Items m_documents;
std::string m_filenameFormat;
doc::ImageBufferPtr m_sampleRenderBuf;
bool m_listTags;
bool m_listLayers;
bool m_listSlices;
Items m_documents;
// Displacement for each tag from/to frames in case we export
// them. It's used in case we trim frames outside tags and they
// will not be exported at all in the final result.
std::map<doc::ObjectId, std::pair<int, int> > m_tagDelta;
// Buffers used
doc::ImageBufferPtr m_docBuf;
doc::ImageBufferPtr m_sampleBuf;
DISABLE_COPYING(DocExporter);
};

View File

@ -21,6 +21,8 @@ namespace app {
void run(base::task::func_t&& func);
// Returns true when the task is completed (whether it was
// canceled or not)
bool completed() const {
return m_task.completed();
}

View File

@ -238,6 +238,14 @@ Editor* UIContext::activeEditor()
return NULL;
}
Editor* UIContext::getEditorFor(Doc* document)
{
if (auto view = getFirstDocView(document))
return view->editor();
else
return nullptr;
}
bool UIContext::hasClosedDocs()
{
return m_closedDocs.hasClosedDocs();

View File

@ -94,14 +94,15 @@ Sprite::~Sprite()
// static
Sprite* Sprite::MakeStdSprite(const ImageSpec& spec,
const int ncolors)
const int ncolors,
const ImageBufferPtr& imageBuf)
{
// Create the sprite.
std::unique_ptr<Sprite> sprite(new Sprite(spec, ncolors));
sprite->setTotalFrames(frame_t(1));
// Create the main image.
ImageRef image(Image::create(spec));
ImageRef image(Image::create(spec, imageBuf));
clear_image(image.get(), 0);
// Create the first transparent layer.

View File

@ -14,6 +14,7 @@
#include "doc/cel_list.h"
#include "doc/color.h"
#include "doc/frame.h"
#include "doc/image_buffer.h"
#include "doc/image_ref.h"
#include "doc/image_spec.h"
#include "doc/layer_list.h"
@ -62,7 +63,8 @@ namespace doc {
// Creates a new sprite with one transparent layer and one cel
// with an image of the size of the sprite.
static Sprite* MakeStdSprite(const ImageSpec& spec,
const int ncolors = 256);
const int ncolors = 256,
const ImageBufferPtr& imageBuf = ImageBufferPtr());
////////////////////////////////////////
// Main properties

View File

@ -1,4 +1,5 @@
// Aseprite UI Library
// Copyright (C) 2019 Igara Studio S.A.
// Copyright (C) 2001-2018 David Capello
//
// This file is released under the terms of the MIT license.
@ -137,12 +138,23 @@ bool ButtonBase::onProcessMessage(Message* msg)
}
case kKeyUpMessage:
if (isEnabled()) {
if (m_behaviorType == kButtonWidget) {
if (isSelected()) {
generateButtonSelectSignal();
if (isEnabled() && hasFocus()) {
switch (m_behaviorType) {
case kButtonWidget:
if (isSelected()) {
generateButtonSelectSignal();
return true;
}
break;
case kCheckWidget: {
// Fire onClick() event
Event ev(this);
onClick(ev);
return true;
}
}
}
break;

View File

@ -620,7 +620,8 @@ void ComboBox::openListBox()
size.w = m_button->bounds().x2() - entryBounds.x - view->border().width();
size.h = viewport->border().height();
for (Widget* item : m_items)
size.h += item->sizeHint().h;
if (!item->hasFlags(HIDDEN))
size.h += item->sizeHint().h;
int max = MAX(entryBounds.y, ui::display_h() - entryBounds.y2()) - 8*guiscale();
size.h = MID(textHeight(), size.h, max);

View File

@ -1,4 +1,5 @@
// Aseprite UI Library
// Copyright (C) 2019 Igara Studio S.A.
// Copyright (C) 2001-2018 David Capello
//
// This file is released under the terms of the MIT license.
@ -10,6 +11,7 @@
#include "ui/listbox.h"
#include "base/clamp.h"
#include "base/fs.h"
#include "ui/listitem.h"
#include "ui/message.h"
@ -115,8 +117,8 @@ void ListBox::selectChild(Widget* item, Message* msg)
ASSERT(i >= 0 && i < int(m_states.size()));
newState = (i >= 0 && i < int(m_states.size()) ? m_states[i]: false);
if (i >= MIN(itemIndex, m_firstSelectedIndex) &&
i <= MAX(itemIndex, m_firstSelectedIndex)) {
if (i >= std::min(itemIndex, m_firstSelectedIndex) &&
i <= std::max(itemIndex, m_firstSelectedIndex)) {
newState = !newState;
}
}
@ -219,13 +221,17 @@ bool ListBox::onProcessMessage(Message* msg)
gfx::Rect vp = view->viewportBounds();
if (mousePos.y < vp.y) {
int num = MAX(1, (vp.y - mousePos.y) / 8);
selectIndex(MID(0, m_lastSelectedIndex-num, getItemsCount()-1), msg);
const int num = std::max(1, (vp.y - mousePos.y) / 8);
const int select =
advanceIndexThroughVisibleItems(m_lastSelectedIndex, -num, false);
selectIndex(select, msg);
pick_item = false;
}
else if (mousePos.y >= vp.y + vp.h) {
int num = MAX(1, (mousePos.y - (vp.y+vp.h-1)) / 8);
selectIndex(MID(0, m_lastSelectedIndex+num, getItemsCount()-1), msg);
const int num = std::max(1, (mousePos.y - (vp.y+vp.h-1)) / 8);
const int select =
advanceIndexThroughVisibleItems(m_lastSelectedIndex, +num, false);
selectIndex(select, msg);
pick_item = false;
}
}
@ -272,7 +278,7 @@ bool ListBox::onProcessMessage(Message* msg)
case kKeyDownMessage:
if (hasFocus() && !children().empty()) {
int select = getSelectedIndex();
int bottom = MAX(0, children().size()-1);
int bottom = std::max(0, int(children().size()-1));
View* view = View::getView(this);
KeyMessage* keymsg = static_cast<KeyMessage*>(msg);
KeyScancode scancode = keymsg->scancode();
@ -338,7 +344,7 @@ bool ListBox::onProcessMessage(Message* msg)
return Widget::onProcessMessage(msg);
}
selectIndex(MID(0, select, bottom), msg);
selectIndex(base::clamp(select, 0, bottom), msg);
return true;
}
break;
@ -384,7 +390,7 @@ void ListBox::onSizeHint(SizeHintEvent& ev)
Size reqSize = child->sizeHint();
w = MAX(w, reqSize.w);
w = std::max(w, reqSize.w);
h += reqSize.h;
++visibles;
}
@ -410,11 +416,11 @@ void ListBox::onDoubleClickItem()
int ListBox::advanceIndexThroughVisibleItems(
int startIndex, int delta, const bool loop)
{
const int bottom = MAX(0, children().size()-1);
const int bottom = std::max(0, int(children().size()-1));
const int sgn = SGN(delta);
int index = startIndex;
startIndex = MID(0, startIndex, bottom);
startIndex = base::clamp(startIndex, 0, bottom);
int lastVisibleIndex = startIndex;
bool cycle = false;