mirror of
https://github.com/aseprite/aseprite.git
synced 2025-03-02 13:14:01 +00:00
Add the final TilesetModes UI: Manual/Semi/Auto
This commit is contained in:
parent
2baf405b37
commit
92b794d457
Binary file not shown.
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 15 KiB |
@ -418,6 +418,10 @@
|
||||
<part id="outline_vertical" x="192" y="224" w="13" h="15" />
|
||||
<part id="outline_empty_pixel" x="208" y="224" w="5" h="5" />
|
||||
<part id="outline_full_pixel" x="214" y="224" w="5" h="5" />
|
||||
<part id="tiles" x="144" y="208" w="6" h="6" />
|
||||
<part id="tiles_manual" x="150" y="208" w="6" h="6" />
|
||||
<part id="tiles_semi" x="156" y="208" w="6" h="6" />
|
||||
<part id="tiles_auto" x="162" y="208" w="6" h="6" />
|
||||
</parts>
|
||||
<styles>
|
||||
<style id="box" />
|
||||
|
@ -447,6 +447,7 @@ add_library(app-lib
|
||||
cmd/move_layer.cpp
|
||||
cmd/patch_cel.cpp
|
||||
cmd/remap_colors.cpp
|
||||
cmd/remap_tiles.cpp
|
||||
cmd/remove_cel.cpp
|
||||
cmd/remove_frame.cpp
|
||||
cmd/remove_layer.cpp
|
||||
|
@ -25,7 +25,7 @@ AddTile::AddTile(doc::Tileset* tileset,
|
||||
: WithTileset(tileset)
|
||||
, WithImage(image.get())
|
||||
, m_size(0)
|
||||
, m_tileIndex(-1)
|
||||
, m_tileIndex(doc::tile_i_notile)
|
||||
, m_imageRef(image)
|
||||
{
|
||||
}
|
||||
@ -82,7 +82,10 @@ void AddTile::onRedo()
|
||||
|
||||
void AddTile::addTile(doc::Tileset* tileset, const doc::ImageRef& image)
|
||||
{
|
||||
m_tileIndex = tileset->add(image);
|
||||
if (m_tileIndex == doc::tile_i_notile)
|
||||
m_tileIndex = tileset->add(image);
|
||||
else
|
||||
tileset->insert(m_tileIndex, image);
|
||||
|
||||
tileset->sprite()->incrementVersion();
|
||||
tileset->incrementVersion();
|
||||
|
62
src/app/cmd/remap_tiles.cpp
Normal file
62
src/app/cmd/remap_tiles.cpp
Normal file
@ -0,0 +1,62 @@
|
||||
// Aseprite
|
||||
// Copyright (C) 2019 Igara Studio S.A.
|
||||
//
|
||||
// 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/cmd/remap_tiles.h"
|
||||
|
||||
#include "doc/cel.h"
|
||||
#include "doc/cels_range.h"
|
||||
#include "doc/image.h"
|
||||
#include "doc/layer.h"
|
||||
#include "doc/layer_tilemap.h"
|
||||
#include "doc/remap.h"
|
||||
#include "doc/sprite.h"
|
||||
#include "doc/tileset.h"
|
||||
|
||||
namespace app {
|
||||
namespace cmd {
|
||||
|
||||
using namespace doc;
|
||||
|
||||
RemapTiles::RemapTiles(Tileset* tileset,
|
||||
const Remap& remap)
|
||||
: WithTileset(tileset)
|
||||
, m_remap(remap)
|
||||
{
|
||||
}
|
||||
|
||||
void RemapTiles::onExecute()
|
||||
{
|
||||
Tileset* tileset = this->tileset();
|
||||
Sprite* spr = tileset->sprite();
|
||||
spr->remapTilemaps(tileset, m_remap);
|
||||
incrementVersions(tileset);
|
||||
}
|
||||
|
||||
void RemapTiles::onUndo()
|
||||
{
|
||||
Tileset* tileset = this->tileset();
|
||||
Sprite* spr = tileset->sprite();
|
||||
spr->remapTilemaps(tileset, m_remap.invert());
|
||||
incrementVersions(tileset);
|
||||
}
|
||||
|
||||
void RemapTiles::incrementVersions(Tileset* tileset)
|
||||
{
|
||||
Sprite* spr = tileset->sprite();
|
||||
for (const Cel* cel : spr->uniqueCels()) {
|
||||
if (cel->layer()->isTilemap() &&
|
||||
static_cast<LayerTilemap*>(cel->layer())->tileset() == tileset) {
|
||||
cel->image()->incrementVersion();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace cmd
|
||||
} // namespace app
|
41
src/app/cmd/remap_tiles.h
Normal file
41
src/app/cmd/remap_tiles.h
Normal file
@ -0,0 +1,41 @@
|
||||
// Aseprite
|
||||
// Copyright (C) 2019 Igara Studio S.A.
|
||||
//
|
||||
// This program is distributed under the terms of
|
||||
// the End-User License Agreement for Aseprite.
|
||||
|
||||
#ifndef APP_CMD_REMAP_TILES_H_INCLUDED
|
||||
#define APP_CMD_REMAP_TILES_H_INCLUDED
|
||||
#pragma once
|
||||
|
||||
#include "app/cmd.h"
|
||||
#include "app/cmd/with_tileset.h"
|
||||
#include "doc/remap.h"
|
||||
|
||||
namespace app {
|
||||
namespace cmd {
|
||||
using namespace doc;
|
||||
|
||||
class RemapTiles : public Cmd
|
||||
, public WithTileset {
|
||||
public:
|
||||
RemapTiles(Tileset* tileset,
|
||||
const Remap& remap);
|
||||
|
||||
protected:
|
||||
void onExecute() override;
|
||||
void onUndo() override;
|
||||
size_t onMemSize() const override {
|
||||
return sizeof(*this) + m_remap.getMemSize();
|
||||
}
|
||||
|
||||
private:
|
||||
void incrementVersions(Tileset* tileset);
|
||||
|
||||
Remap m_remap;
|
||||
};
|
||||
|
||||
} // namespace cmd
|
||||
} // namespace app
|
||||
|
||||
#endif
|
@ -1,4 +1,5 @@
|
||||
// Aseprite
|
||||
// Copyright (C) 2019 Igara Studio S.A.
|
||||
// Copyright (C) 2016 David Capello
|
||||
//
|
||||
// This program is distributed under the terms of
|
||||
@ -29,7 +30,7 @@ TrimCel::TrimCel(Cel* cel)
|
||||
cel->image()->maskColor(),
|
||||
cel->layer(), newBounds)) {
|
||||
newBounds.offset(cel->position());
|
||||
if (cel->bounds() != newBounds) {
|
||||
if (cel->imageBounds() != newBounds) {
|
||||
add(new cmd::CropCel(cel, newBounds));
|
||||
}
|
||||
}
|
||||
|
@ -349,9 +349,9 @@ Engine::Engine()
|
||||
lua_newtable(L);
|
||||
lua_pushvalue(L, -1);
|
||||
lua_setglobal(L, "TilesetMode");
|
||||
setfield_integer(L, "LOCKED", TilesetMode::Locked);
|
||||
setfield_integer(L, "MODIFY_EXISTENT", TilesetMode::ModifyExistent);
|
||||
setfield_integer(L, "GENERATE_ALL_TILES", TilesetMode::GenerateAllTiles);
|
||||
setfield_integer(L, "MANUAL", TilesetMode::Manual);
|
||||
setfield_integer(L, "SEMI", TilesetMode::Semi);
|
||||
setfield_integer(L, "AUTO", TilesetMode::Auto);
|
||||
lua_pop(L, 1);
|
||||
|
||||
// Register classes/prototypes
|
||||
|
@ -52,7 +52,7 @@ namespace app {
|
||||
, m_sprite(nullptr)
|
||||
, m_layer(nullptr)
|
||||
, m_frame(0)
|
||||
, m_tilesetMode(TilesetMode::Locked) { }
|
||||
, m_tilesetMode(TilesetMode::Manual) { }
|
||||
|
||||
const Focus focus() const { return m_focus; }
|
||||
bool inEditor() const { return m_focus == InEditor; }
|
||||
|
@ -13,10 +13,9 @@ namespace app {
|
||||
// These modes are available edition modes for the tileset when an
|
||||
// tilemap is edited.
|
||||
enum class TilesetMode {
|
||||
Locked, // Cannot edit the tileset (don't modify or add new tiles)
|
||||
ModifyExistent, // Modify existent tiles (don't create new ones)
|
||||
//GenerateNewTiles, // Auto-generate new tiles, edit existent ones
|
||||
GenerateAllTiles, // Auto-generate new and modified tiles
|
||||
Manual, // Modify existent tiles (don't create new ones)
|
||||
Semi, // Auto-generate new tiles if needed, edit existent ones
|
||||
Auto, // Auto-generate new and modified tiles
|
||||
};
|
||||
|
||||
} // namespace app
|
||||
|
@ -88,12 +88,6 @@ enum class PalButton {
|
||||
MAX
|
||||
};
|
||||
|
||||
enum class TilesButton {
|
||||
MODE,
|
||||
AUTO,
|
||||
MAX
|
||||
};
|
||||
|
||||
using namespace app::skin;
|
||||
using namespace ui;
|
||||
|
||||
@ -147,7 +141,8 @@ ColorBar* ColorBar::m_instance = NULL;
|
||||
ColorBar::ColorBar(int align, TooltipManager* tooltipManager)
|
||||
: Box(align)
|
||||
, m_buttons(int(PalButton::MAX))
|
||||
, m_tilesButtons(int(TilesButton::MAX))
|
||||
, m_tilesButton(1)
|
||||
, m_tilesetModeButtons(3)
|
||||
, m_splitter(Splitter::ByPercentage, VERTICAL)
|
||||
, m_paletteView(true, PaletteView::FgBgColors, this, 16)
|
||||
, m_tilesView(true, PaletteView::FgBgTiles, this, 16)
|
||||
@ -170,7 +165,7 @@ ColorBar::ColorBar(int align, TooltipManager* tooltipManager)
|
||||
, m_lastButtons(kButtonLeft)
|
||||
, m_editMode(false)
|
||||
, m_tilesMode(false)
|
||||
, m_autoTilesMode(true)
|
||||
, m_tilesetMode(TilesetMode::Semi)
|
||||
, m_redrawTimer(250, this)
|
||||
, m_redrawAll(false)
|
||||
, m_implantChange(false)
|
||||
@ -185,10 +180,12 @@ ColorBar::ColorBar(int align, TooltipManager* tooltipManager)
|
||||
m_buttons.addItem(theme->parts.palPresets());
|
||||
m_buttons.addItem(theme->parts.palOptions());
|
||||
|
||||
m_tilesButtons.addItem("Tiles");
|
||||
m_tilesButtons.addItem("Auto");
|
||||
m_tilesButtons.setVisible(false);
|
||||
m_tilesButtons.getItem((int)TilesButton::AUTO)->setHotColor(theme->colors.editPalFace());
|
||||
m_tilesButton.addItem(theme->parts.tiles());
|
||||
|
||||
m_tilesetModeButtons.addItem(theme->parts.tilesManual());
|
||||
m_tilesetModeButtons.addItem(theme->parts.tilesSemi());
|
||||
m_tilesetModeButtons.addItem(theme->parts.tilesAuto());
|
||||
setTilesetMode(m_tilesetMode);
|
||||
|
||||
m_paletteView.setColumns(8);
|
||||
m_tilesView.setColumns(8);
|
||||
@ -213,8 +210,11 @@ ColorBar::ColorBar(int align, TooltipManager* tooltipManager)
|
||||
setColorSelector(
|
||||
Preferences::instance().colorBar.selector());
|
||||
|
||||
m_tilesHBox.addChild(&m_tilesButton);
|
||||
m_tilesHBox.addChild(&m_tilesetModeButtons);
|
||||
|
||||
addChild(&m_buttons);
|
||||
addChild(&m_tilesButtons);
|
||||
addChild(&m_tilesHBox);
|
||||
addChild(&m_splitter);
|
||||
|
||||
HBox* fgBox = new HBox;
|
||||
@ -239,7 +239,8 @@ ColorBar::ColorBar(int align, TooltipManager* tooltipManager)
|
||||
m_bgWarningIcon->Click.connect(base::Bind<void>(&ColorBar::onFixWarningClick, this, &m_bgColor, m_bgWarningIcon));
|
||||
m_redrawTimer.Tick.connect(base::Bind<void>(&ColorBar::onTimerTick, this));
|
||||
m_buttons.ItemChange.connect(base::Bind<void>(&ColorBar::onPaletteButtonClick, this));
|
||||
m_tilesButtons.ItemChange.connect(base::Bind<void>(&ColorBar::onTilesButtonClick, this));
|
||||
m_tilesButton.ItemChange.connect(base::Bind<void>(&ColorBar::onTilesButtonClick, this));
|
||||
m_tilesetModeButtons.ItemChange.connect(base::Bind<void>(&ColorBar::onTilesetModeButtonClick, this));
|
||||
|
||||
tooltipManager->addTooltipFor(&m_fgColor, "Foreground color", LEFT);
|
||||
tooltipManager->addTooltipFor(&m_bgColor, "Background color", LEFT);
|
||||
@ -257,11 +258,14 @@ ColorBar::ColorBar(int align, TooltipManager* tooltipManager)
|
||||
m_bgColor.resetSizeHint();
|
||||
m_fgColor.setSizeHint(0, m_fgColor.sizeHint().h);
|
||||
m_bgColor.setSizeHint(0, m_bgColor.sizeHint().h);
|
||||
m_buttons.setMinSize(gfx::Size(0, theme->dimensions.colorBarButtonsHeight()));
|
||||
m_buttons.setMaxSize(gfx::Size(std::numeric_limits<int>::max(),
|
||||
std::numeric_limits<int>::max())); // TODO add resetMaxSize
|
||||
m_buttons.setMaxSize(gfx::Size(m_buttons.sizeHint().w,
|
||||
theme->dimensions.colorBarButtonsHeight()));
|
||||
|
||||
for (auto w : { &m_buttons, &m_tilesButton, &m_tilesetModeButtons }) {
|
||||
w->setMinSize(gfx::Size(0, theme->dimensions.colorBarButtonsHeight()));
|
||||
w->setMaxSize(gfx::Size(std::numeric_limits<int>::max(),
|
||||
std::numeric_limits<int>::max())); // TODO add resetMaxSize
|
||||
w->setMaxSize(gfx::Size(m_buttons.sizeHint().w,
|
||||
theme->dimensions.colorBarButtonsHeight()));
|
||||
}
|
||||
|
||||
// Change color-bar background color (not ColorBar::setBgColor)
|
||||
this->Widget::setBgColor(theme->colors.tabActiveFace());
|
||||
@ -444,8 +448,7 @@ void ColorBar::setEditMode(bool state)
|
||||
m_editMode = state;
|
||||
item->setIcon(state ? theme->parts.timelineOpenPadlockActive():
|
||||
theme->parts.timelineClosedPadlockNormal());
|
||||
item->setHotColor(state ? theme->colors.editPalFace():
|
||||
gfx::ColorNone);
|
||||
item->setHotColor(state ? theme->colors.editPalFace(): gfx::ColorNone);
|
||||
|
||||
// Deselect color entries when we cancel editing
|
||||
if (!state)
|
||||
@ -456,7 +459,7 @@ bool ColorBar::inTilesMode() const
|
||||
{
|
||||
return
|
||||
(m_tilesMode &&
|
||||
m_tilesButtons.isVisible() &&
|
||||
m_tilesHBox.isVisible() &&
|
||||
m_lastDocument &&
|
||||
m_lastDocument->sprite());
|
||||
}
|
||||
@ -467,10 +470,11 @@ void ColorBar::setTilesMode(bool state)
|
||||
const bool isTilemap = (site.layer() && site.layer()->isTilemap());
|
||||
|
||||
SkinTheme* theme = static_cast<SkinTheme*>(this->theme());
|
||||
ButtonSet::Item* item = m_tilesButtons.getItem((int)TilesButton::MODE);
|
||||
ButtonSet::Item* item = m_tilesButton.getItem(0);
|
||||
|
||||
m_tilesMode = state;
|
||||
item->setHotColor(state ? theme->colors.editPalFace(): gfx::ColorNone);
|
||||
item->setMono(true);
|
||||
|
||||
if (state && isTilemap) {
|
||||
manager()->freeWidget(&m_paletteView);
|
||||
@ -486,30 +490,25 @@ void ColorBar::setTilesMode(bool state)
|
||||
layout();
|
||||
}
|
||||
|
||||
bool ColorBar::inAutoTilesMode() const
|
||||
{
|
||||
return
|
||||
(m_autoTilesMode &&
|
||||
m_tilesButtons.isVisible() &&
|
||||
m_lastDocument &&
|
||||
m_lastDocument->sprite());
|
||||
}
|
||||
|
||||
void ColorBar::setAutoTilesMode(bool state)
|
||||
{
|
||||
SkinTheme* theme = static_cast<SkinTheme*>(this->theme());
|
||||
ButtonSet::Item* item = m_tilesButtons.getItem((int)TilesButton::AUTO);
|
||||
|
||||
m_autoTilesMode = state;
|
||||
item->setHotColor(state ? theme->colors.editPalFace(): gfx::ColorNone);
|
||||
}
|
||||
|
||||
TilesetMode ColorBar::tilesetMode() const
|
||||
{
|
||||
if (inAutoTilesMode())
|
||||
return TilesetMode::GenerateAllTiles;
|
||||
if (m_tilesHBox.isVisible() &&
|
||||
m_lastDocument &&
|
||||
m_lastDocument->sprite()) {
|
||||
return m_tilesetMode;
|
||||
}
|
||||
else
|
||||
return TilesetMode::ModifyExistent;
|
||||
return TilesetMode::Manual;
|
||||
}
|
||||
|
||||
void ColorBar::setTilesetMode(const TilesetMode mode)
|
||||
{
|
||||
m_tilesetMode = mode;
|
||||
|
||||
for (int i=0; i<3; ++i) {
|
||||
ButtonSet::Item* item = m_tilesetModeButtons.getItem(i);
|
||||
item->setSelected(int(mode) == i);
|
||||
}
|
||||
}
|
||||
|
||||
void ColorBar::onActiveSiteChange(const Site& site)
|
||||
@ -527,20 +526,20 @@ void ColorBar::onActiveSiteChange(const Site& site)
|
||||
}
|
||||
|
||||
bool isTilemap = false;
|
||||
if (site.layer()) {
|
||||
if (site.layer())
|
||||
isTilemap = site.layer()->isTilemap();
|
||||
if (m_tilesButtons.isVisible() != isTilemap) {
|
||||
m_tilesButtons.setVisible(isTilemap);
|
||||
layout();
|
||||
}
|
||||
|
||||
if (isTilemap) {
|
||||
doc::ObjectId newTilesetId =
|
||||
static_cast<const doc::LayerTilemap*>(site.layer())->tileset()->id();
|
||||
if (m_lastTilesetId != newTilesetId) {
|
||||
m_lastTilesetId = newTilesetId;
|
||||
m_scrollableTilesView.updateView();
|
||||
}
|
||||
if (m_tilesHBox.isVisible() != isTilemap) {
|
||||
m_tilesHBox.setVisible(isTilemap);
|
||||
layout();
|
||||
}
|
||||
|
||||
if (isTilemap) {
|
||||
doc::ObjectId newTilesetId =
|
||||
static_cast<const doc::LayerTilemap*>(site.layer())->tileset()->id();
|
||||
if (m_lastTilesetId != newTilesetId) {
|
||||
m_lastTilesetId = newTilesetId;
|
||||
m_scrollableTilesView.updateView();
|
||||
}
|
||||
}
|
||||
if (!isTilemap) {
|
||||
@ -563,7 +562,6 @@ void ColorBar::onTilesetChanged(DocEvent& ev)
|
||||
if (!ui::is_ui_thread())
|
||||
return;
|
||||
|
||||
m_tilesButtons.deselectItems();
|
||||
if (m_scrollableTilesView.isVisible())
|
||||
m_scrollableTilesView.updateView();
|
||||
|
||||
@ -639,20 +637,15 @@ void ColorBar::onPaletteButtonClick()
|
||||
|
||||
void ColorBar::onTilesButtonClick()
|
||||
{
|
||||
int item = m_tilesButtons.selectedItem();
|
||||
m_tilesButtons.deselectItems();
|
||||
m_tilesButton.deselectItems();
|
||||
setTilesMode(!inTilesMode());
|
||||
}
|
||||
|
||||
switch (static_cast<TilesButton>(item)) {
|
||||
|
||||
case TilesButton::MODE:
|
||||
setTilesMode(!inTilesMode());
|
||||
break;
|
||||
|
||||
case TilesButton::AUTO:
|
||||
setAutoTilesMode(!inAutoTilesMode());
|
||||
break;
|
||||
|
||||
}
|
||||
void ColorBar::onTilesetModeButtonClick()
|
||||
{
|
||||
int item = m_tilesetModeButtons.selectedItem();
|
||||
m_tilesetModeButtons.deselectItems();
|
||||
setTilesetMode(static_cast<TilesetMode>(item));
|
||||
}
|
||||
|
||||
void ColorBar::onRemapButtonClick()
|
||||
@ -1398,9 +1391,13 @@ void ColorBar::setupTooltips(TooltipManager* tooltipManager)
|
||||
tooltipManager->addTooltipFor(&m_remapButton, "Matches old indexes with new indexes", BOTTOM);
|
||||
|
||||
tooltipManager->addTooltipFor(
|
||||
m_tilesButtons.getItem((int)TilesButton::MODE), "Show Tileset", BOTTOM);
|
||||
m_tilesButton.getItem(0), "Show/Hide Tileset", BOTTOM);
|
||||
tooltipManager->addTooltipFor(
|
||||
m_tilesButtons.getItem((int)TilesButton::AUTO), "Generate new tiles automatically", BOTTOM);
|
||||
m_tilesetModeButtons.getItem((int)TilesetMode::Manual), "Manual-mode: Modify existent tiles,\ndon't create new tiles automatically", BOTTOM);
|
||||
tooltipManager->addTooltipFor(
|
||||
m_tilesetModeButtons.getItem((int)TilesetMode::Semi), "Semi-mode: Modify and reuse existent tiles,\ncreate/delete tiles if needed/possible", BOTTOM);
|
||||
tooltipManager->addTooltipFor(
|
||||
m_tilesetModeButtons.getItem((int)TilesetMode::Auto), "Auto-mode: Don't modify existent tiles,\ngenerate new tiles automatically only", BOTTOM);
|
||||
}
|
||||
|
||||
// static
|
||||
|
@ -83,10 +83,8 @@ namespace app {
|
||||
bool inTilesMode() const;
|
||||
void setTilesMode(bool state);
|
||||
|
||||
bool inAutoTilesMode() const;
|
||||
void setAutoTilesMode(bool state);
|
||||
|
||||
TilesetMode tilesetMode() const;
|
||||
void setTilesetMode(const TilesetMode mode);
|
||||
|
||||
ColorButton* fgColorButton() { return &m_fgColor; }
|
||||
ColorButton* bgColorButton() { return &m_bgColor; }
|
||||
@ -120,6 +118,7 @@ namespace app {
|
||||
void onAfterExecuteCommand(CommandExecutionEvent& ev);
|
||||
void onPaletteButtonClick();
|
||||
void onTilesButtonClick();
|
||||
void onTilesetModeButtonClick();
|
||||
void onRemapButtonClick();
|
||||
void onPaletteIndexChange(PaletteIndexChangeEvent& ev);
|
||||
void onFgColorChangeFromPreferences();
|
||||
@ -170,7 +169,9 @@ namespace app {
|
||||
class WarningIcon;
|
||||
|
||||
ButtonSet m_buttons;
|
||||
ButtonSet m_tilesButtons;
|
||||
ui::HBox m_tilesHBox;
|
||||
ButtonSet m_tilesButton;
|
||||
ButtonSet m_tilesetModeButtons;
|
||||
std::unique_ptr<PalettePopup> m_palettePopup;
|
||||
ui::Splitter m_splitter;
|
||||
ui::VBox m_palettePlaceholder;
|
||||
@ -217,7 +218,7 @@ namespace app {
|
||||
|
||||
// True if we should be putting/setting tiles.
|
||||
bool m_tilesMode;
|
||||
bool m_autoTilesMode;
|
||||
TilesetMode m_tilesetMode;
|
||||
|
||||
// Timer to redraw editors after a palette change.
|
||||
ui::Timer m_redrawTimer;
|
||||
|
@ -766,8 +766,12 @@ void PaletteView::onPaint(ui::PaintEvent& ev)
|
||||
|
||||
const int size = m_adapter->size();
|
||||
for (int i=0; i<size; ++i) {
|
||||
if (!m_selectedEntries[i])
|
||||
// TODO why does this fail?
|
||||
//ASSERT(i >= 0 && i < m_selectedEntries.size());
|
||||
if (i >= 0 && i < m_selectedEntries.size() &&
|
||||
!m_selectedEntries[i]) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const int k = (dragging ? m_hot.color+j: i);
|
||||
|
||||
|
@ -15,6 +15,8 @@
|
||||
#include "app/cmd/clear_cel.h"
|
||||
#include "app/cmd/clear_mask.h"
|
||||
#include "app/cmd/copy_region.h"
|
||||
#include "app/cmd/remap_tiles.h"
|
||||
#include "app/cmd/remove_tile.h"
|
||||
#include "app/cmd/replace_image.h"
|
||||
#include "app/cmd/set_cel_position.h"
|
||||
#include "app/cmd_sequence.h"
|
||||
@ -40,6 +42,7 @@
|
||||
#include "render/quantization.h"
|
||||
#include "render/render.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <memory>
|
||||
|
||||
@ -403,7 +406,8 @@ void modify_tilemap_cel_region(
|
||||
newTilemapBounds.x, newTilemapBounds.y, newTilemapBounds.w, newTilemapBounds.h);
|
||||
|
||||
// Autogenerate tiles
|
||||
if (tilesetMode == TilesetMode::GenerateAllTiles) {
|
||||
if (tilesetMode == TilesetMode::Auto ||
|
||||
tilesetMode == TilesetMode::Semi) {
|
||||
doc::TilesetHashTable hashImages; // TODO the hashImages should be inside the Tileset
|
||||
{
|
||||
// Add existent tiles in the hash table
|
||||
@ -434,6 +438,8 @@ void modify_tilemap_cel_region(
|
||||
regionToPatch -= gfx::Region(grid.tileToCanvas(oldTilemapBounds));
|
||||
regionToPatch |= region;
|
||||
|
||||
std::vector<bool> modifiedTileIndexes(tileset->size(), false);
|
||||
|
||||
for (const gfx::Point& tilePt : grid.tilesInCanvasRegion(regionToPatch)) {
|
||||
const int u = tilePt.x-newTilemapBounds.x;
|
||||
const int v = tilePt.y-newTilemapBounds.y;
|
||||
@ -445,6 +451,9 @@ void modify_tilemap_cel_region(
|
||||
const doc::tile_index ti = doc::tile_geti(t);
|
||||
const doc::ImageRef existenTileImage = tileset->get(ti);
|
||||
|
||||
if (tilesetMode == TilesetMode::Semi)
|
||||
modifiedTileIndexes[ti] = true;
|
||||
|
||||
const gfx::Rect tileInCanvasRc(grid.tileToCanvas(tilePt), tileSize);
|
||||
ImageRef tileImage(getTileImage(existenTileImage, tileInCanvasRc));
|
||||
if (grid.hasMask())
|
||||
@ -455,6 +464,9 @@ void modify_tilemap_cel_region(
|
||||
auto it = hashImages.find(tileImage);
|
||||
if (it != hashImages.end()) {
|
||||
tileIndex = it->second; // TODO
|
||||
|
||||
if (tilesetMode == TilesetMode::Semi)
|
||||
modifiedTileIndexes[tileIndex] = false;
|
||||
}
|
||||
else {
|
||||
auto addTile = new cmd::AddTile(tileset, tileImage);
|
||||
@ -464,6 +476,8 @@ void modify_tilemap_cel_region(
|
||||
hashImages[tileImage] = tileIndex;
|
||||
}
|
||||
|
||||
OPS_TRACE(" - tile %d -> %d\n", ti, tileIndex);
|
||||
|
||||
const doc::tile_t tile = doc::tile(tileIndex, 0);
|
||||
if (t != tile) {
|
||||
newTilemap->putPixel(u, v, tile);
|
||||
@ -471,8 +485,6 @@ void modify_tilemap_cel_region(
|
||||
}
|
||||
}
|
||||
|
||||
doc->notifyTilesetChanged(tileset);
|
||||
|
||||
if (newTilemap->width() != cel->image()->width() ||
|
||||
newTilemap->height() != cel->image()->height()) {
|
||||
gfx::Point newPos = grid.tileToCanvas(newTilemapBounds.origin());
|
||||
@ -491,9 +503,17 @@ void modify_tilemap_cel_region(
|
||||
tilePtsRgn,
|
||||
gfx::Point(0, 0)));
|
||||
}
|
||||
|
||||
// Remove unused tiles
|
||||
if (tilesetMode == TilesetMode::Semi) {
|
||||
// TODO reuse tiles that will be removed in the algorithm above
|
||||
remove_unused_tiles_from_tileset(cmds, tileset, modifiedTileIndexes);
|
||||
}
|
||||
|
||||
doc->notifyTilesetChanged(tileset);
|
||||
}
|
||||
// Modify active set of tiles manually / don't auto-generate new tiles
|
||||
else if (tilesetMode == TilesetMode::ModifyExistent) {
|
||||
else if (tilesetMode == TilesetMode::Manual) {
|
||||
for (const gfx::Point& tilePt : grid.tilesInCanvasRegion(region)) {
|
||||
// Ignore modifications outside the tilemap
|
||||
if (!cel->image()->bounds().contains(tilePt.x, tilePt.y))
|
||||
@ -567,4 +587,59 @@ void clear_mask_from_cel(CmdSequence* cmds,
|
||||
}
|
||||
}
|
||||
|
||||
void remove_unused_tiles_from_tileset(
|
||||
CmdSequence* cmds,
|
||||
doc::Tileset* tileset,
|
||||
std::vector<bool>& unusedTiles)
|
||||
{
|
||||
OPS_TRACE("remove_unused_tiles_from_tileset\n");
|
||||
|
||||
int n = tileset->size();
|
||||
|
||||
for (Cel* cel : tileset->sprite()->cels()) {
|
||||
if (!cel->layer()->isTilemap() ||
|
||||
static_cast<LayerTilemap*>(cel->layer())->tileset() != tileset)
|
||||
continue;
|
||||
|
||||
Image* tilemapImage = cel->image();
|
||||
for_each_pixel<TilemapTraits>(
|
||||
tilemapImage,
|
||||
[&unusedTiles, &n](const doc::tile_t t) {
|
||||
const doc::tile_index ti = doc::tile_geti(t);
|
||||
n = std::max<int>(n, ti+1);
|
||||
if (ti >= 0 &&
|
||||
ti < int(unusedTiles.size()) &&
|
||||
unusedTiles[ti]) {
|
||||
unusedTiles[ti] = false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
doc::Remap remap(n);
|
||||
doc::tile_index ti, tj;
|
||||
ti = tj = 0;
|
||||
for (; ti<remap.size(); ++ti) {
|
||||
OPS_TRACE(" - ti=%d tj=%d unusedTiles[%d]=%d\n",
|
||||
ti, tj, ti, (ti < unusedTiles.size() && unusedTiles[ti] ? 1: 0));
|
||||
if (ti < unusedTiles.size() &&
|
||||
unusedTiles[ti]) {
|
||||
cmds->executeAndAdd(new cmd::RemoveTile(tileset, tj));
|
||||
// Map to nothing, so the map can be invertible
|
||||
remap.map(ti, doc::Remap::kNoMap);
|
||||
}
|
||||
else {
|
||||
remap.map(ti, tj++);
|
||||
}
|
||||
}
|
||||
|
||||
if (!remap.isIdentity()) {
|
||||
#ifdef _DEBUG
|
||||
for (ti=0; ti<remap.size(); ++ti) {
|
||||
OPS_TRACE(" - remap tile[%d] -> %d\n", ti, remap[ti]);
|
||||
}
|
||||
#endif
|
||||
cmds->executeAndAdd(new cmd::RemapTiles(tileset, remap));
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace app
|
||||
|
@ -17,12 +17,14 @@
|
||||
#include "gfx/region.h"
|
||||
|
||||
#include <functional>
|
||||
#include <vector>
|
||||
|
||||
namespace doc {
|
||||
class Cel;
|
||||
class Layer;
|
||||
class LayerTilemap;
|
||||
class Sprite;
|
||||
class Tileset;
|
||||
}
|
||||
|
||||
namespace app {
|
||||
@ -68,6 +70,13 @@ namespace app {
|
||||
doc::Cel* cel,
|
||||
const TilesetMode tilesetMode);
|
||||
|
||||
// unusedTiles is a set of possibles tiles to check if they are
|
||||
// really unused by all tilemaps.
|
||||
void remove_unused_tiles_from_tileset(
|
||||
CmdSequence* cmds,
|
||||
doc::Tileset* tileset,
|
||||
std::vector<bool>& unusedTiles);
|
||||
|
||||
} // namespace app
|
||||
|
||||
#endif
|
||||
|
@ -39,6 +39,8 @@ namespace doc {
|
||||
const gfx::RectF& boundsF() const { return m_data->boundsF(); }
|
||||
int opacity() const { return m_data->opacity(); }
|
||||
|
||||
gfx::Rect imageBounds() const { return m_data->imageBounds(); }
|
||||
|
||||
LayerImage* layer() const { return m_layer; }
|
||||
Image* image() const { return m_data->image(); }
|
||||
ImageRef imageRef() const { return m_data->imageRef(); }
|
||||
|
@ -33,6 +33,17 @@ namespace doc {
|
||||
Image* image() const { return const_cast<Image*>(m_image.get()); };
|
||||
ImageRef imageRef() const { return m_image; }
|
||||
|
||||
// Returns a rectangle with the bounds of the image (width/height
|
||||
// of the image) in the position of the cel (useful to compare
|
||||
// active tilemap bounds when we have to change the tilemap cel
|
||||
// bounds).
|
||||
gfx::Rect imageBounds() const {
|
||||
return gfx::Rect(m_bounds.x,
|
||||
m_bounds.y,
|
||||
m_image->width(),
|
||||
m_image->height());
|
||||
}
|
||||
|
||||
void setImage(const ImageRef& image, Layer* layer);
|
||||
void setPosition(const gfx::Point& pos);
|
||||
void setOpacity(int opacity) { m_opacity = opacity; }
|
||||
|
@ -1,5 +1,6 @@
|
||||
// Aseprite Document Library
|
||||
// Copyright (c) 2001-2014 David Capello
|
||||
// Copyright (C) 2019 Igara Studio S.A.
|
||||
// Copyright (C) 2001-2014 David Capello
|
||||
//
|
||||
// This file is released under the terms of the MIT license.
|
||||
// Read LICENSE.txt for more information.
|
||||
@ -8,6 +9,8 @@
|
||||
#define DOC_IMAGE_BITS_H_INCLUDED
|
||||
#pragma once
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
namespace doc {
|
||||
|
||||
class Image;
|
||||
@ -152,6 +155,20 @@ namespace doc {
|
||||
LockImageBits(); // Undefined
|
||||
};
|
||||
|
||||
template<class ImageTraits,
|
||||
class UnaryFunction>
|
||||
inline void for_each_pixel(const Image* image, UnaryFunction f) {
|
||||
const LockImageBits<ImageTraits> bits(image);
|
||||
std::for_each(bits.begin(), bits.end(), f);
|
||||
}
|
||||
|
||||
template<class ImageTraits,
|
||||
class UnaryOperation>
|
||||
inline void transform_image(Image* image, UnaryOperation f) {
|
||||
LockImageBits<ImageTraits> bits(image);
|
||||
std::transform(bits.begin(), bits.end(), bits.begin(), f);
|
||||
}
|
||||
|
||||
} // namespace doc
|
||||
|
||||
#endif
|
||||
|
@ -413,17 +413,24 @@ bool is_same_image(const Image* i1, const Image* i2)
|
||||
|
||||
void remap_image(Image* image, const Remap& remap)
|
||||
{
|
||||
ASSERT(image->pixelFormat() == IMAGE_INDEXED);
|
||||
if (image->pixelFormat() != IMAGE_INDEXED)
|
||||
return;
|
||||
ASSERT(image->pixelFormat() == IMAGE_INDEXED ||
|
||||
image->pixelFormat() == IMAGE_TILEMAP);
|
||||
|
||||
LockImageBits<IndexedTraits> bits(image);
|
||||
LockImageBits<IndexedTraits>::iterator
|
||||
it = bits.begin(),
|
||||
end = bits.end();
|
||||
|
||||
for (; it != end; ++it)
|
||||
*it = remap[*it];
|
||||
switch (image->pixelFormat()) {
|
||||
case IMAGE_INDEXED:
|
||||
transform_image<IndexedTraits>(
|
||||
image, [&remap](color_t c) -> color_t {
|
||||
return remap[c];
|
||||
});
|
||||
break;
|
||||
case IMAGE_TILEMAP:
|
||||
transform_image<TilemapTraits>(
|
||||
image, [&remap](color_t c) -> color_t {
|
||||
ASSERT(remap[c] != Remap::kNoMap);
|
||||
return remap[c];
|
||||
});
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// TODO test this hash routine and find a better alternative
|
||||
|
@ -1,6 +1,6 @@
|
||||
// Aseprite Document Library
|
||||
// Copyright (c) 2020 Igara Studio S.A.
|
||||
// Copyright (c) 2001-2016 David Capello
|
||||
// Copyright (C) 2019-2020 Igara Studio S.A.
|
||||
// Copyright (C) 2001-2016 David Capello
|
||||
//
|
||||
// This file is released under the terms of the MIT license.
|
||||
// Read LICENSE.txt for more information.
|
||||
@ -135,8 +135,12 @@ void Remap::merge(const Remap& other)
|
||||
Remap Remap::invert() const
|
||||
{
|
||||
Remap inv(size());
|
||||
for (int i=0; i<size(); ++i)
|
||||
inv.map(operator[](i), i);
|
||||
for (int i=0; i<size(); ++i) {
|
||||
int j = m_map[i];
|
||||
if (j == kNoMap)
|
||||
continue;
|
||||
inv.map(j, i);
|
||||
}
|
||||
return inv;
|
||||
}
|
||||
|
||||
@ -160,6 +164,9 @@ bool Remap::isInvertible(const PalettePicks& usedEntries) const
|
||||
continue;
|
||||
|
||||
int j = m_map[i];
|
||||
if (j == kNoMap)
|
||||
continue;
|
||||
|
||||
if (picks[j])
|
||||
return false;
|
||||
|
||||
@ -168,4 +175,13 @@ bool Remap::isInvertible(const PalettePicks& usedEntries) const
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Remap::isIdentity() const
|
||||
{
|
||||
for (int i=0; i<size(); ++i) {
|
||||
if (m_map[i] != i)
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace doc
|
||||
|
@ -1,5 +1,6 @@
|
||||
// Aseprite Document Library
|
||||
// Copyright (c) 2001-2017 David Capello
|
||||
// Copyright (C) 2019 Igara Studio S.A.
|
||||
// Copyright (C) 2001-2017 David Capello
|
||||
//
|
||||
// This file is released under the terms of the MIT license.
|
||||
// Read LICENSE.txt for more information.
|
||||
@ -18,6 +19,8 @@ namespace doc {
|
||||
|
||||
class Remap {
|
||||
public:
|
||||
static const int kNoMap = -1;
|
||||
|
||||
Remap(int entries = 1) : m_map(entries, 0) { }
|
||||
|
||||
int size() const {
|
||||
@ -27,7 +30,10 @@ namespace doc {
|
||||
// Maps input "fromIndex" value, to "toIndex" output.
|
||||
void map(int fromIndex, int toIndex) {
|
||||
ASSERT(fromIndex >= 0 && fromIndex < size());
|
||||
ASSERT(toIndex >= 0 && toIndex < size());
|
||||
// toIndex = kNoMap means (there is no remap for this value, useful
|
||||
// to ignore this entry when we invert the map)
|
||||
ASSERT(toIndex == kNoMap ||
|
||||
toIndex >= 0 && toIndex < size());
|
||||
|
||||
m_map[fromIndex] = toIndex;
|
||||
}
|
||||
@ -57,6 +63,10 @@ namespace doc {
|
||||
// undo data, without saving all images' pixels.
|
||||
bool isInvertible(const PalettePicks& usedEntries) const;
|
||||
|
||||
// Returns true if the remap does nothing (each map entry is
|
||||
// matched to itself).
|
||||
bool isIdentity() const;
|
||||
|
||||
private:
|
||||
std::vector<int> m_map;
|
||||
};
|
||||
|
@ -525,6 +525,17 @@ void Sprite::remapImages(const Remap& remap)
|
||||
remap_image(image.get(), remap);
|
||||
}
|
||||
|
||||
void Sprite::remapTilemaps(const Tileset* tileset,
|
||||
const Remap& remap)
|
||||
{
|
||||
for (Cel* cel : uniqueCels()) {
|
||||
if (cel->layer()->isTilemap() &&
|
||||
static_cast<LayerTilemap*>(cel->layer())->tileset() == tileset) {
|
||||
remap_image(cel->image(), remap);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
// Drawing
|
||||
|
||||
|
@ -43,6 +43,7 @@ namespace doc {
|
||||
class Remap;
|
||||
class RgbMap;
|
||||
class SelectedFrames;
|
||||
class Tileset;
|
||||
class Tilesets;
|
||||
|
||||
typedef std::vector<Palette*> PalettesList;
|
||||
@ -172,6 +173,8 @@ namespace doc {
|
||||
void getImages(std::vector<ImageRef>& images) const;
|
||||
|
||||
void remapImages(const Remap& remap);
|
||||
void remapTilemaps(const Tileset* tileset,
|
||||
const Remap& remap);
|
||||
void pickCels(const double x,
|
||||
const double y,
|
||||
const frame_t frame,
|
||||
|
@ -20,6 +20,7 @@ namespace doc {
|
||||
const uint32_t tile_i_shift = 0; // Tile index
|
||||
const uint32_t tile_f_shift = 28; // Flags (flip, rotation)
|
||||
|
||||
const uint32_t tile_i_notile = 0xffffffff;
|
||||
const uint32_t tile_i_mask = 0x1fffffff;
|
||||
const uint32_t tile_f_mask = 0xe0000000; // 3 flags
|
||||
const uint32_t tile_f_flipx = 0x20000000;
|
||||
|
@ -43,16 +43,16 @@ namespace doc {
|
||||
tile_index size() const { return tile_index(m_tiles.size()); }
|
||||
void resize(const tile_index ntiles);
|
||||
|
||||
ImageRef get(const tile_index index) const {
|
||||
if (index < size())
|
||||
return m_tiles[index];
|
||||
ImageRef get(const tile_index ti) const {
|
||||
if (ti < size())
|
||||
return m_tiles[ti];
|
||||
else
|
||||
return ImageRef(nullptr);
|
||||
}
|
||||
|
||||
void set(const tile_index index,
|
||||
void set(const tile_index ti,
|
||||
const ImageRef& image) {
|
||||
m_tiles[index] = image;
|
||||
m_tiles[ti] = image;
|
||||
}
|
||||
|
||||
tile_index add(const ImageRef& image) {
|
||||
@ -60,6 +60,12 @@ namespace doc {
|
||||
return tile_t(m_tiles.size()-1);
|
||||
}
|
||||
|
||||
void insert(const tile_index ti,
|
||||
const ImageRef& image) {
|
||||
ASSERT(ti <= size());
|
||||
m_tiles.insert(m_tiles.begin()+ti, image);
|
||||
}
|
||||
|
||||
void erase(const tile_index ti) {
|
||||
ASSERT(ti >= 0 && ti < size());
|
||||
m_tiles.erase(m_tiles.begin()+ti);
|
||||
|
Loading…
x
Reference in New Issue
Block a user