Add support to show multiple sections in Export Sprite Sheet

This commit is contained in:
David Capello 2019-10-29 16:37:06 -03:00
parent f43f7e2ada
commit c2acc973ee
13 changed files with 228 additions and 76 deletions

View File

@ -310,6 +310,7 @@
<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" />
<option id="sections" type="std::string" />
</section>
<section id="gif">
<option id="show_alert" type="bool" default="true" />

View File

@ -538,6 +538,11 @@ sprite = Sprite
sprite_tooltip = Source of sprite samples to export in the sprite sheet
borders = Borders
borders_tooltip = Add or trim borders from each sprite in the sprite sheet
expand_all_sections_tooltip = <<<END
Show all sections
Use Ctrl+click to display multiple sections at the same time
END
layout = Layout
layout_tooltip = Arrangement of sprites in the final result
output = Output

View File

@ -9,19 +9,46 @@
<hbox>
<separator horizontal="true" expansive="true" />
<buttonset id="section_tabs" columns="4">
<item id="sectin_sprite_tab" text="@.sprite" tooltip="@.sprite_tooltip" tooltip_dir="bottom" selected="true" />
<item id="sectin_borders_tab" text="@.borders" tooltip="@.borders_tooltip" tooltip_dir="bottom" />
<item id="sectin_layout_tab" text="@.layout" tooltip="@.layout_tooltip" tooltip_dir="bottom" />
<item id="sectin_output_tab" text="@.output" tooltip="@.output_tooltip" tooltip_dir="bottom" />
<buttonset id="section_tabs" columns="4" oneormore="true">
<item id="section_layout_tab" text="@.layout" tooltip="@.layout_tooltip" tooltip_dir="bottom" />
<item id="section_sprite_tab" text="@.sprite" tooltip="@.sprite_tooltip" tooltip_dir="bottom" selected="true" />
<item id="section_borders_tab" text="@.borders" tooltip="@.borders_tooltip" tooltip_dir="bottom" />
<item id="section_output_tab" text="@.output" tooltip="@.output_tooltip" tooltip_dir="bottom" />
</buttonset>
<separator horizontal="true" expansive="true" />
<button id="expand_sections" icon="window_center_icon"
tooltip="@.expand_all_sections_tooltip" tooltip_dir="bottom" />
</hbox>
<panel id="panel" expansive="true">
<!-- Layout -->
<grid id="section_layout" columns="4" expansive="true">
<label text="@.sheet_type" />
<combobox id="sheet_type" expansive="true" cell_hspan="3" cell_align="horizontal"
tooltip="@.sheet_type_tooltip" tooltip_dir="bottom" />
<label text="@.constraints" />
<hbox cell_hspan="3">
<combobox id="constraint_type" tooltip="@.constraints_tooltip" />
<expr id="width_constraint" />
<expr id="height_constraint" />
</hbox>
<hbox />
<hbox cell_hspan="3">
<check id="merge_dups" text="@.merge_dups" tooltip="@.merge_dups_tooltip" />
<check id="ignore_empty" text="@.ignore_empty" tooltip="@.ignore_empty_tooltip" />
</hbox>
</grid>
<!-- Sprite -->
<hbox id="section_sprite_separator">
<separator horizontal="true" text="@.sprite" expansive="true" />
<button id="close_sprite_section" icon="window_close_icon" />
</hbox>
<grid id="section_sprite" columns="4" expansive="true">
<label text="@.layers" />
<combobox id="layers" text="" cell_hspan="2" cell_align="horizontal" />
@ -34,6 +61,10 @@
<!-- Borders -->
<hbox id="section_borders_separator">
<separator horizontal="true" text="@.borders" expansive="true" />
<button id="close_borders_section" icon="window_close_icon" />
</hbox>
<grid id="section_borders" columns="4" expansive="true">
<hbox />
@ -68,29 +99,12 @@
</grid>
<!-- Layout -->
<grid id="section_layout" columns="4" expansive="true">
<label text="@.sheet_type" />
<combobox id="sheet_type" expansive="true" cell_hspan="3" cell_align="horizontal"
tooltip="@.sheet_type_tooltip" tooltip_dir="bottom" />
<label text="@.constraints" />
<hbox cell_hspan="3">
<combobox id="constraint_type" tooltip="@.constraints_tooltip" />
<expr id="width_constraint" />
<expr id="height_constraint" />
</hbox>
<hbox />
<hbox cell_hspan="3">
<check id="merge_dups" text="@.merge_dups" tooltip="@.merge_dups_tooltip" />
<check id="ignore_empty" text="@.ignore_empty" tooltip="@.ignore_empty_tooltip" />
</hbox>
</grid>
<!-- Output -->
<hbox id="section_output_separator">
<separator horizontal="true" text="@.output" expansive="true" />
<button id="close_output_section" icon="window_close_icon" />
</hbox>
<grid id="section_output" columns="4" expansive="true">
<check id="image_enabled" text="@.output_file" />
<button id="image_filename" cell_hspan="3" cell_align="horizontal" />
@ -122,7 +136,6 @@
</panel>
<separator horizontal="true" cell_hspan="4" />
<hbox cell_hspan="4">
<boxfiller />
<hbox>

View File

@ -21,6 +21,7 @@
#include "app/i18n/strings.h"
#include "app/job.h"
#include "app/modules/editors.h"
#include "app/modules/gui.h"
#include "app/pref/preferences.h"
#include "app/restore_visible_layers.h"
#include "app/task.h"
@ -39,6 +40,7 @@
#include "doc/layer.h"
#include "doc/tag.h"
#include "fmt/format.h"
#include "ui/message.h"
#include "ui/system.h"
#include "export_sprite_sheet.xml.h"
@ -86,9 +88,9 @@ struct ExportSpriteSheetParams : public NewParams {
#ifdef ENABLE_UI
enum Section {
kSectionLayout,
kSectionSprite,
kSectionBorders,
kSectionLayout,
kSectionOutput,
};
@ -288,7 +290,8 @@ class ExportSpriteSheetWindow : public app::gen::ExportSpriteSheet {
public:
ExportSpriteSheetWindow(DocExporter& exporter,
Site& site,
ExportSpriteSheetParams& params)
ExportSpriteSheetParams& params,
Preferences& pref)
: m_exporter(exporter)
, m_frontBuffer(std::make_shared<doc::ImageBuffer>())
, m_backBuffer(std::make_shared<doc::ImageBuffer>())
@ -302,6 +305,10 @@ public:
, m_filenameFormat(params.filenameFormat())
{
sectionTabs()->ItemChange.connect(base::Bind<void>(&ExportSpriteSheetWindow::onChangeSection, this));
expandSections()->Click.connect(base::Bind<void>(&ExportSpriteSheetWindow::onExpandSections, this));
closeSpriteSection()->Click.connect(base::Bind<void>(&ExportSpriteSheetWindow::onCloseSection, this, kSectionSprite));
closeBordersSection()->Click.connect(base::Bind<void>(&ExportSpriteSheetWindow::onCloseSection, this, kSectionBorders));
closeOutputSection()->Click.connect(base::Bind<void>(&ExportSpriteSheetWindow::onCloseSection, this, kSectionOutput));
static_assert(
(int)app::SpriteSheetType::None == 0 &&
@ -397,7 +404,7 @@ public:
if (m_filename.empty() ||
m_filename == kSpecifiedFilename) {
std::string defExt = Preferences::instance().spriteSheet.defaultExtension();
std::string defExt = pref.spriteSheet.defaultExtension();
if (base::utf8_icmp(base::get_file_extension(site.document()->filename()), defExt) == 0)
m_filename = base + "-sheet." + defExt;
@ -436,13 +443,30 @@ public:
preview()->Click.connect(base::Bind<void>(&ExportSpriteSheetWindow::generatePreview, this));
m_genTimer.Tick.connect(base::Bind<void>(&ExportSpriteSheetWindow::onGenTimerTick, this));
// Select tabs
{
const std::string s = pref.spriteSheet.sections();
const bool layout = (s.find("layout") != std::string::npos);
const bool sprite = (s.find("sprite") != std::string::npos);
const bool borders = (s.find("borders") != std::string::npos);
const bool output = (s.find("output") != std::string::npos);
sectionTabs()->getItem(kSectionLayout)->setSelected(layout || (!sprite & !borders && !output));
sectionTabs()->getItem(kSectionSprite)->setSelected(sprite);
sectionTabs()->getItem(kSectionBorders)->setSelected(borders);
sectionTabs()->getItem(kSectionOutput)->setSelected(output);
}
onChangeSection();
onSheetTypeChange();
onFileNamesChange();
updateExportButton();
preview()->setSelected(Preferences::instance().spriteSheet.preview());
preview()->setSelected(pref.spriteSheet.preview());
generatePreview();
remapWindow();
centerWindow();
load_window_pos(this, "ExportSpriteSheet");
}
~ExportSpriteSheetWindow() {
@ -456,6 +480,19 @@ public:
}
}
std::string selectedSectionsString() const {
const bool layout = sectionTabs()->getItem(kSectionLayout)->isSelected();
const bool sprite = sectionTabs()->getItem(kSectionSprite)->isSelected();
const bool borders = sectionTabs()->getItem(kSectionBorders)->isSelected();
const bool output = sectionTabs()->getItem(kSectionOutput)->isSelected();
return
fmt::format("{} {} {} {}",
(layout ? "layout": ""),
(sprite ? "sprite": ""),
(borders ? "borders": ""),
(output ? "output": ""));
}
bool ok() const {
return closer() == exportButton();
}
@ -491,6 +528,15 @@ public:
private:
bool onProcessMessage(ui::Message* msg) override {
switch (msg->type()) {
case kCloseMessage:
save_window_pos(this, "ExportSpriteSheet");
break;
}
return Window::onProcessMessage(msg);
}
void onBroadcastMouseMessage(WidgetsList& targets) override {
Window::onBroadcastMouseMessage(targets);
@ -500,17 +546,38 @@ private:
}
void onChangeSection() {
Widget* section = nullptr;
switch (sectionTabs()->selectedItem()) {
case kSectionSprite: section = sectionSprite(); break;
case kSectionBorders: section = sectionBorders(); break;
case kSectionLayout: section = sectionLayout(); break;
case kSectionOutput: section = sectionOutput(); break;
}
panel()->showChild(section);
panel()->showAllChildren();
const bool layout = sectionTabs()->getItem(kSectionLayout)->isSelected();
const bool sprite = sectionTabs()->getItem(kSectionSprite)->isSelected();
const bool borders = sectionTabs()->getItem(kSectionBorders)->isSelected();
const bool output = sectionTabs()->getItem(kSectionOutput)->isSelected();
sectionLayout()->setVisible(layout);
sectionSpriteSeparator()->setVisible(sprite && layout);
sectionSprite()->setVisible(sprite);
sectionBordersSeparator()->setVisible(borders && (layout || sprite));
sectionBorders()->setVisible(borders);
sectionOutputSeparator()->setVisible(output && (layout || sprite || borders));
sectionOutput()->setVisible(output);
resize();
}
void onExpandSections() {
sectionTabs()->getItem(kSectionLayout)->setSelected(true);
sectionTabs()->getItem(kSectionSprite)->setSelected(true);
sectionTabs()->getItem(kSectionBorders)->setSelected(true);
sectionTabs()->getItem(kSectionOutput)->setSelected(true);
onChangeSection();
}
void onCloseSection(const Section section) {
if (sectionTabs()->countSelectedItems() > 1)
sectionTabs()->getItem(section)->setSelected(false);
onChangeSection();
}
app::SpriteSheetType spriteSheetTypeValue() const {
return (app::SpriteSheetType)(sheetType()->getSelectedItemIndex()+1);
}
@ -1158,14 +1225,19 @@ void ExportSpriteSheetCommand::onExecute(Context* context)
bool askOverwrite = params.askOverwrite();
if (showUI) {
ExportSpriteSheetWindow window(exporter, site, params);
auto& pref = Preferences::instance();
ExportSpriteSheetWindow window(exporter, site, params, pref);
window.openWindowInForeground();
// Save global sprite sheet generation settings anyway (even if
// the user cancel the dialog, the global settings are stored).
pref.spriteSheet.preview(window.preview()->isSelected());
pref.spriteSheet.sections(window.selectedSectionsString());
if (!window.ok())
return;
// Preview sprite sheet generation
Preferences::instance().spriteSheet.preview(window.preview()->isSelected());
window.updateParams(params);
docPref.spriteSheet.defined(true);
docPref.spriteSheet.type (params.type());

View File

@ -1,4 +1,5 @@
// Aseprite
// Copyright (C) 2019 Igara Studio S.A.
// Copyright (C) 2001-2018 David Capello
//
// This program is distributed under the terms of
@ -41,7 +42,7 @@ FilterTargetButtons::FilterTargetButtons(int imgtype, bool withChannels)
, m_index(nullptr)
, m_cels(nullptr)
{
setMultipleSelection(true);
setMultiMode(MultiMode::Set);
addChild(&m_tooltips);
if (withChannels) {

View File

@ -1,5 +1,5 @@
// Aseprite
// Copyright (C) 2018 Igara Studio S.A.
// Copyright (C) 2018-2019 Igara Studio S.A.
// Copyright (C) 2001-2017 David Capello
//
// This program is distributed under the terms of
@ -299,7 +299,7 @@ ButtonSet::ButtonSet(int columns)
: Grid(columns, false)
, m_offerCapture(true)
, m_triggerOnMouseUp(false)
, m_multipleSelection(false)
, m_multiMode(MultiMode::One)
{
InitTheme.connect(
[this]{
@ -357,6 +357,15 @@ int ButtonSet::selectedItem() const
return -1;
}
int ButtonSet::countSelectedItems() const
{
int count = 0;
for (auto child : children())
if (child->isSelected())
++count;
return count;
}
void ButtonSet::setSelectedItem(int index, bool focusItem)
{
if (index >= 0 && index < (int)children().size())
@ -372,16 +381,38 @@ void ButtonSet::setSelectedItem(Item* item, bool focusItem)
void ButtonSet::onSelectItem(Item* item, bool focusItem, ui::Message* msg)
{
if (!m_multipleSelection) {
if (item && item->isSelected())
const int count = countSelectedItems();
if ((m_multiMode == MultiMode::One) ||
(m_multiMode == MultiMode::OneOrMore &&
msg &&
!msg->shiftPressed() &&
!msg->altPressed() &&
!msg->ctrlPressed() &&
!msg->cmdPressed())) {
if (item && item->isSelected() &&
((m_multiMode == MultiMode::One) ||
(m_multiMode == MultiMode::OneOrMore && count == 1)))
return;
Item* sel = findSelectedItem();
if (sel)
sel->setSelected(false);
if (m_multiMode == MultiMode::One) {
if (auto sel = findSelectedItem())
sel->setSelected(false);
}
else if (m_multiMode == MultiMode::OneOrMore) {
for (auto child : children())
child->setSelected(false);
}
}
if (item) {
if (m_multiMode == MultiMode::OneOrMore) {
// Item already selected
if (count == 1 && item == findSelectedItem())
return;
}
// Toggle item
item->setSelected(!item->isSelected());
if (focusItem)
item->requestFocus();
@ -405,9 +436,9 @@ void ButtonSet::setTriggerOnMouseUp(bool state)
m_triggerOnMouseUp = state;
}
void ButtonSet::setMultipleSelection(bool state)
void ButtonSet::setMultiMode(MultiMode mode)
{
m_multipleSelection = state;
m_multiMode = mode;
}
void ButtonSet::onItemChange(Item* item)

View File

@ -1,4 +1,5 @@
// Aseprite
// Copyright (C) 2019 Igara Studio S.A.
// Copyright (C) 2001-2018 David Capello
//
// This program is distributed under the terms of
@ -38,6 +39,12 @@ namespace app {
gfx::Color m_hotColor;
};
enum class MultiMode {
One, // Only one button can be selected (like radio buttons)
Set, // Each button is a checkbox
OneOrMore, // One click selects one button, ctrl+click multiple selections
};
ButtonSet(int columns);
Item* addItem(const std::string& text, int hspan = 1, int vspan = 1);
@ -47,6 +54,7 @@ namespace app {
int getItemIndex(const Item* item) const;
int selectedItem() const;
int countSelectedItems() const;
Item* findSelectedItem() const;
void setSelectedItem(int index, bool focusItem = true);
void setSelectedItem(Item* item, bool focusItem = true);
@ -54,7 +62,7 @@ namespace app {
void setOfferCapture(bool state);
void setTriggerOnMouseUp(bool state);
void setMultipleSelection(bool state);
void setMultiMode(MultiMode mode);
obs::signal<void(Item*)> ItemChange;
obs::signal<void(Item*)> RightClick;
@ -67,7 +75,7 @@ namespace app {
private:
bool m_offerCapture;
bool m_triggerOnMouseUp;
bool m_multipleSelection;
MultiMode m_multiMode;
};
} // namespace app

View File

@ -125,15 +125,6 @@ ColorPopup::CustomButtonSet::CustomButtonSet()
{
}
int ColorPopup::CustomButtonSet::countSelectedItems()
{
int count = 0;
for (int i=0; i<COLOR_MODES; ++i)
if (getItem(i)->isSelected())
++count;
return count;
}
void ColorPopup::CustomButtonSet::onSelectItem(Item* item, bool focusItem, ui::Message* msg)
{
int count = countSelectedItems();

View File

@ -71,7 +71,6 @@ namespace app {
class CustomButtonSet : public ButtonSet {
public:
CustomButtonSet();
int countSelectedItems();
private:
void onSelectItem(Item* item, bool focusItem, ui::Message* msg) override;
};

View File

@ -1107,7 +1107,7 @@ protected:
class ContextBar::SymmetryField : public ButtonSet {
public:
SymmetryField() : ButtonSet(2) {
setMultipleSelection(true);
setMultiMode(MultiMode::Set);
SkinTheme* theme = SkinTheme::instance();
addItem(theme->parts.horizontalSymmetry());
addItem(theme->parts.verticalSymmetry());

View File

@ -1,4 +1,5 @@
// Aseprite
// Copyright (C) 2019 Igara Studio S.A.
// Copyright (C) 2001-2018 David Capello
//
// This program is distributed under the terms of
@ -451,9 +452,10 @@ Widget* WidgetLoader::convertXmlElementToWidget(const TiXmlElement* elem, Widget
widget = new ButtonSet(strtol(columns, NULL, 10));
if (ButtonSet* buttonset = dynamic_cast<ButtonSet*>(widget)) {
bool multiple = bool_attr_is_true(elem, "multiple");
if (multiple)
buttonset->setMultipleSelection(multiple);
if (bool multiple = bool_attr_is_true(elem, "multiple"))
buttonset->setMultiMode(ButtonSet::MultiMode::Set);
if (bool oneormore = bool_attr_is_true(elem, "oneormore"))
buttonset->setMultiMode(ButtonSet::MultiMode::OneOrMore);
}
}
else if (elem_name == "item") {

View File

@ -1,5 +1,6 @@
// Aseprite UI Library
// Copyright (C) 2001-2013, 2015 David Capello
// Copyright (C) 2019 Igara Studio S.A.
// Copyright (C) 2001-2015 David Capello
//
// This file is released under the terms of the MIT license.
// Read LICENSE.txt for more information.
@ -13,15 +14,18 @@
#include "ui/resize_event.h"
#include "ui/size_hint_event.h"
#include <algorithm>
namespace ui {
Panel::Panel()
: Widget(kPanelWidget)
: m_multiple(false)
{
}
void Panel::showChild(Widget* widget)
{
m_multiple = false;
for (auto child : children()) {
if (!child->isDecorative())
child->setVisible(child == widget);
@ -29,8 +33,23 @@ void Panel::showChild(Widget* widget)
layout();
}
void Panel::showAllChildren()
{
m_multiple = true;
for (auto child : children()) {
if (!child->isDecorative())
child->setVisible(true);
}
layout();
}
void Panel::onResize(ResizeEvent& ev)
{
if (m_multiple) {
VBox::onResize(ev);
return;
}
// Copy the new position rectangle
setBoundsQuietly(ev.bounds());
@ -44,6 +63,11 @@ void Panel::onResize(ResizeEvent& ev)
void Panel::onSizeHint(SizeHintEvent& ev)
{
if (m_multiple) {
VBox::onSizeHint(ev);
return;
}
gfx::Size maxSize(0, 0);
gfx::Size reqSize;
@ -51,13 +75,13 @@ void Panel::onSizeHint(SizeHintEvent& ev)
if (!child->isDecorative()) {
reqSize = child->sizeHint();
maxSize.w = MAX(maxSize.w, reqSize.w);
maxSize.h = MAX(maxSize.h, reqSize.h);
maxSize.w = std::max(maxSize.w, reqSize.w);
maxSize.h = std::max(maxSize.h, reqSize.h);
}
}
if (hasText())
maxSize.w = MAX(maxSize.w, textWidth());
maxSize.w = std::max(maxSize.w, textWidth());
ev.setSizeHint(
maxSize.w + border().width(),

View File

@ -1,4 +1,5 @@
// Aseprite UI Library
// Copyright (C) 2019 Igara Studio S.A.
// Copyright (C) 2001-2014 David Capello
//
// This file is released under the terms of the MIT license.
@ -8,19 +9,23 @@
#define UI_PANEL_H_INCLUDED
#pragma once
#include "ui/widget.h"
#include "ui/box.h"
namespace ui {
class Panel : public Widget {
class Panel : public VBox {
public:
Panel();
void showChild(Widget* widget);
void showAllChildren();
protected:
virtual void onResize(ResizeEvent& ev) override;
virtual void onSizeHint(SizeHintEvent& ev) override;
private:
bool m_multiple;
};
} // namespace ui