diff --git a/README.md b/README.md
index 099981f25..e81506521 100644
--- a/README.md
+++ b/README.md
@@ -50,12 +50,14 @@ You can ask for help in:
## Authors
-[Igara Studio](https://www.igarastudio.com/) is developing Aseprite:
+Aseprite is being developed by [Igara Studio](https://www.igarastudio.com/):
-* [David Capello](https://davidcapello.com/): Lead developer, fixing
- issues, new features, and user support.
-* [Gaspar Capello](https://github.com/Gasparoken): Developer, fixing
- issues and new features.
+* [David Capello](https://davidcapello.com/): Lead developer,
+ bug fixing & new features in desktop & web, and user support.
+* [Gaspar Capello](https://github.com/Gasparoken): Developer,
+ bug fixing & new features in desktop, and user support.
+* [Martin Capello](https://github.com/martincapello): Developer,
+ new store website.
## Credits
diff --git a/data/gui.xml b/data/gui.xml
index 19e157635..fdf1b2aa2 100644
--- a/data/gui.xml
+++ b/data/gui.xml
@@ -1109,10 +1109,13 @@
-
-
+
+ -
+
+
-
diff --git a/data/pref.xml b/data/pref.xml
index 1c7c7ed7b..e0789c447 100644
--- a/data/pref.xml
+++ b/data/pref.xml
@@ -364,6 +364,12 @@
+
diff --git a/data/strings/en.ini b/data/strings/en.ini
index b1fdccc19..6d4452ef7 100644
--- a/data/strings/en.ini
+++ b/data/strings/en.ini
@@ -1386,9 +1386,10 @@ ryb_color_wheel = RYB Color Wheel
normal_map_color_wheel = Normal Map Color Wheel
load_palette = L&oad Palette
save_palette = S&ave Palette
+save_palette_as_preset = Save Palette as Preset
load_default_palette = Load Default Palette
-save_as_default_palette = Save as Default Palette
-create_palette_from_current_sprite = Create Palette from Current Sprite
+save_as_default_palette = Save Palette as Default
+create_palette_from_current_sprite = New Palette from Sprite
[palette_size]
title = Palette Size
@@ -1541,6 +1542,12 @@ title = TGA Options
bits_per_pixel = Bits Per Pixel
compress = Compress
+[css_options]
+title = CSS Options
+pixel_scale = Pixel Scale
+with_vars = Use CSS3 Variables
+generate_html = Generate Sample HTML File
+
[timeline_conf]
position = Position:
left = &Left
diff --git a/data/widgets/about.xml b/data/widgets/about.xml
index 221d342f7..0fa59cc9d 100644
--- a/data/widgets/about.xml
+++ b/data/widgets/about.xml
@@ -1,17 +1,19 @@
-
+
-
+
-
+
-
+
+
+
@@ -20,14 +22,14 @@
-
+
-
+
diff --git a/data/widgets/css_options.xml b/data/widgets/css_options.xml
new file mode 100644
index 000000000..df55b180a
--- /dev/null
+++ b/data/widgets/css_options.xml
@@ -0,0 +1,25 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/data/widgets/options.xml b/data/widgets/options.xml
index cb51d04a0..867fc4d59 100644
--- a/data/widgets/options.xml
+++ b/data/widgets/options.xml
@@ -440,6 +440,7 @@
pref="scripts.show_run_script_alert" />
+
diff --git a/docs/CODING_STYLE.md b/docs/CODING_STYLE.md
index ddf3762ff..dc4188665 100644
--- a/docs/CODING_STYLE.md
+++ b/docs/CODING_STYLE.md
@@ -25,7 +25,7 @@ void global_function(int arg1, int arg2,
do {
...
- } while (condition);
+ } while (condition);
switch (condition) {
case 1:
diff --git a/src/app/CMakeLists.txt b/src/app/CMakeLists.txt
index a841b4172..33d981ad2 100644
--- a/src/app/CMakeLists.txt
+++ b/src/app/CMakeLists.txt
@@ -125,6 +125,7 @@ endif()
set(file_formats
file/ase_format.cpp
file/bmp_format.cpp
+ file/css_format.cpp
file/fli_format.cpp
file/gif_format.cpp
file/ico_format.cpp
@@ -606,7 +607,7 @@ add_library(app-lib
tools/pick_ink.cpp
tools/point_shape.cpp
tools/stroke.cpp
- tools/symmetries.cpp
+ tools/symmetry.cpp
tools/tool_box.cpp
tools/tool_loop_manager.cpp
tools/velocity.cpp
diff --git a/src/app/app.h b/src/app/app.h
index f255fae2a..e178ee519 100644
--- a/src/app/app.h
+++ b/src/app/app.h
@@ -124,6 +124,7 @@ namespace app {
obs::signal Exit;
obs::signal PaletteChange;
obs::signal ColorSpaceChange;
+ obs::signal PalettePresetsChange;
private:
class CoreModules;
diff --git a/src/app/commands/cmd_options.cpp b/src/app/commands/cmd_options.cpp
index 1ded3a332..e33383442 100644
--- a/src/app/commands/cmd_options.cpp
+++ b/src/app/commands/cmd_options.cpp
@@ -992,6 +992,7 @@ private:
advancedModeAlert()->resetWithDefaultValue();
invalidFgBgColorAlert()->resetWithDefaultValue();
runScriptAlert()->resetWithDefaultValue();
+ cssOptionsAlert()->resetWithDefaultValue();
gifOptionsAlert()->resetWithDefaultValue();
jpegOptionsAlert()->resetWithDefaultValue();
svgOptionsAlert()->resetWithDefaultValue();
diff --git a/src/app/commands/cmd_remove_layer.cpp b/src/app/commands/cmd_remove_layer.cpp
index a36d6532f..0ee577521 100644
--- a/src/app/commands/cmd_remove_layer.cpp
+++ b/src/app/commands/cmd_remove_layer.cpp
@@ -1,5 +1,5 @@
// Aseprite
-// Copyright (C) 2020 Igara Studio S.A.
+// Copyright (C) 2020-2021 Igara Studio S.A.
// Copyright (C) 2001-2018 David Capello
//
// This program is distributed under the terms of
@@ -78,7 +78,7 @@ void RemoveLayerCommand::onExecute(Context* context)
}
}
else {
- if (sprite->allLayersCount() == 1) {
+ if (sprite->root()->layersCount() == 1) {
ui::Alert::show(Strings::alerts_cannot_delete_all_layers());
return;
}
diff --git a/src/app/commands/cmd_save_palette.cpp b/src/app/commands/cmd_save_palette.cpp
index 429599162..05be7186d 100644
--- a/src/app/commands/cmd_save_palette.cpp
+++ b/src/app/commands/cmd_save_palette.cpp
@@ -8,6 +8,7 @@
#include "config.h"
#endif
+#include "app/app.h"
#include "app/commands/cmd_set_palette.h"
#include "app/commands/commands.h"
#include "app/commands/params.h"
@@ -35,6 +36,7 @@ protected:
private:
std::string m_preset;
+ bool m_saveAsPreset = false;
};
SavePaletteCommand::SavePaletteCommand()
@@ -45,6 +47,7 @@ SavePaletteCommand::SavePaletteCommand()
void SavePaletteCommand::onLoadParams(const Params& params)
{
m_preset = params.get("preset");
+ m_saveAsPreset = (params.get("saveAsPreset") == "true");
}
void SavePaletteCommand::onExecute(Context* context)
@@ -58,8 +61,9 @@ void SavePaletteCommand::onExecute(Context* context)
else {
base::paths exts = get_writable_palette_extensions();
base::paths selFilename;
+ std::string initialPath = (m_saveAsPreset ? get_preset_palettes_dir(): "");
if (!app::show_file_selector(
- "Save Palette", "", exts,
+ "Save Palette", initialPath, exts,
FileSelectorType::Save, selFilename))
return;
@@ -74,6 +78,9 @@ void SavePaletteCommand::onExecute(Context* context)
if (!context->activeDocument())
set_current_palette(palette, false);
}
+ if (m_saveAsPreset) {
+ App::instance()->PalettePresetsChange();
+ }
}
Command* CommandFactory::createSavePaletteCommand()
diff --git a/src/app/crash/session.cpp b/src/app/crash/session.cpp
index ba4e6e271..b02f4331a 100644
--- a/src/app/crash/session.cpp
+++ b/src/app/crash/session.cpp
@@ -1,5 +1,5 @@
// Aseprite
-// Copyright (C) 2019-2020 Igara Studio S.A.
+// Copyright (C) 2019-2021 Igara Studio S.A.
// Copyright (C) 2001-2018 David Capello
//
// This program is distributed under the terms of
@@ -332,18 +332,13 @@ Doc* Session::restoreBackupRawImages(Backup* backup,
void Session::deleteBackup(Backup* backup)
{
- try {
- auto it = std::find(m_backups.begin(), m_backups.end(), backup);
- ASSERT(it != m_backups.end());
- if (it != m_backups.end())
- m_backups.erase(it);
+ auto it = std::find(m_backups.begin(), m_backups.end(), backup);
+ ASSERT(it != m_backups.end());
+ if (it != m_backups.end())
+ m_backups.erase(it);
- if (base::is_directory(backup->dir()))
- deleteDirectory(backup->dir());
- }
- catch (const std::exception& ex) {
- Console::showException(ex);
- }
+ if (base::is_directory(backup->dir()))
+ deleteDirectory(backup->dir());
}
void Session::loadPid()
diff --git a/src/app/doc.cpp b/src/app/doc.cpp
index a9f85a375..56e0d684e 100644
--- a/src/app/doc.cpp
+++ b/src/app/doc.cpp
@@ -32,6 +32,7 @@
#include "doc/palette.h"
#include "doc/sprite.h"
#include "doc/tag.h"
+#include "doc/slice.h"
#include "os/display.h"
#include "os/system.h"
#include "ui/system.h"
@@ -519,6 +520,14 @@ Doc* Doc::duplicate(DuplicateType type) const
for (const Tag* tag : sourceSprite->tags())
spriteCopy->tags().add(new Tag(*tag));
+ // Copy slices
+ for (const Slice *slice : sourceSprite->slices()) {
+ auto sliceCopy = new Slice(*slice);
+ spriteCopy->slices().add(sliceCopy);
+
+ ASSERT(sliceCopy->owner() == &spriteCopy->slices());
+ }
+
// Copy color palettes
{
PalettesList::const_iterator it = sourceSprite->getPalettes().begin();
diff --git a/src/app/file/css_format.cpp b/src/app/file/css_format.cpp
new file mode 100644
index 000000000..0b4087505
--- /dev/null
+++ b/src/app/file/css_format.cpp
@@ -0,0 +1,303 @@
+// Aseprite
+// Copyright (c) 2018-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/console.h"
+#include "app/context.h"
+#include "app/doc.h"
+#include "app/file/file.h"
+#include "app/file/file_format.h"
+#include "app/file/format_options.h"
+#include "app/pref/preferences.h"
+#include "base/convert_to.h"
+#include "base/cfile.h"
+#include "base/file_handle.h"
+#include "base/string.h"
+#include "doc/doc.h"
+#include "ui/window.h"
+
+#include "css_options.xml.h"
+
+
+namespace app {
+
+using namespace base;
+
+class CssFormat : public FileFormat {
+ class CssOptions : public FormatOptions {
+ public:
+ CssOptions() : pixelScale(1), gutterSize(0),
+ generateHtml(false),
+ withVars(false) { }
+ int pixelScale;
+ int gutterSize;
+ bool generateHtml;
+ bool withVars;
+ };
+
+ const char* onGetName() const override {
+ return "css";
+ }
+
+ void onGetExtensions(base::paths& exts) const override {
+ exts.push_back("css");
+ }
+
+ dio::FileFormat onGetDioFormat() const override {
+ return dio::FileFormat::CSS_STYLE;
+ }
+
+ int onGetFlags() const override {
+ return
+ FILE_SUPPORT_SAVE |
+ FILE_SUPPORT_RGB |
+ FILE_SUPPORT_RGBA |
+ FILE_SUPPORT_GRAY |
+ FILE_SUPPORT_GRAYA |
+ FILE_SUPPORT_INDEXED |
+ FILE_SUPPORT_SEQUENCES |
+ FILE_SUPPORT_GET_FORMAT_OPTIONS |
+ FILE_SUPPORT_PALETTE_WITH_ALPHA;
+ }
+
+ bool onLoad(FileOp* fop) override;
+#ifdef ENABLE_SAVE
+ bool onSave(FileOp* fop) override;
+#endif
+ FormatOptionsPtr onAskUserForFormatOptions(FileOp* fop) override;
+};
+
+FileFormat *CreateCssFormat()
+{
+ return new CssFormat;
+}
+
+bool CssFormat::onLoad(FileOp* fop)
+{
+ return false;
+}
+
+#ifdef ENABLE_SAVE
+
+bool CssFormat::onSave(FileOp* fop)
+{
+ const Image* image = fop->sequenceImage();
+ int x, y, c, r, g, b, a, alpha;
+ const auto css_options = std::static_pointer_cast(fop->formatOptions());
+ FileHandle handle(open_file_with_exception_sync_on_close(fop->filename(), "wb"));
+ FILE* f = handle.get();
+ auto print_color = [f](int r, int g, int b, int a) {
+ if (a == 255) {
+ fprintf(f, "#%02X%02X%02X", r, g, b);
+ }
+ else {
+ fprintf(f, "rgba(%d, %d, %d, %d)", r, g, b, a);
+ }
+ };
+ auto print_shadow_color = [f, css_options, print_color](int x, int y, int r,
+ int g, int b, int a,
+ bool comma = true) {
+ fprintf(f, comma?",\n":"\n");
+ if (css_options->withVars) {
+ fprintf(f, "\tcalc(%d*var(--shadow-mult)) calc(%d*var(--shadow-mult)) var(--blur) var(--spread) ",
+ x, y);
+ }
+ else {
+ int x_loc = x * (css_options->pixelScale + css_options->gutterSize);
+ int y_loc = y * (css_options->pixelScale + css_options->gutterSize);
+ fprintf(f, "%dpx %dpx ", x_loc, y_loc);
+ }
+ print_color(r, g, b, a);
+ };
+ auto print_shadow_index = [f, css_options](int x, int y, int i, bool comma=true) {
+ fprintf(f, comma?",\n":"\n");
+ fprintf(f, "\tcalc(%d*var(--shadow-mult)) calc(%d*var(--shadow-mult)) var(--blur) var(--spread) var(--color-%d)",
+ x, y, i);
+ };
+ if (css_options->withVars) {
+ fprintf(f, ":root {\n"
+ "\t--blur: 0px;\n"
+ "\t--spread: 0px;\n"
+ "\t--pixel-size: %dpx;\n"
+ "\t--gutter-size: %dpx;\n",
+ css_options->pixelScale,
+ css_options->gutterSize);
+ fprintf(f, "\t--shadow-mult: calc(var(--gutter-size) + var(--pixel-size));\n");
+ if (image->pixelFormat() == IMAGE_INDEXED) {
+ for (y = 0; y < 256; y++) {
+ fop->sequenceGetColor(y, &r, &g, &b);
+ fop->sequenceGetAlpha(y, &a);
+ fprintf(f, "\t--color-%d: ", y);
+ print_color(r, g, b, a);
+ fprintf(f, ";\n");
+ }
+ }
+ fprintf(f, "}\n\n");
+ }
+
+ fprintf(f, ".pixel-art {\n");
+ fprintf(f, "\tposition: relative;\n");
+ fprintf(f, "\ttop: 0;\n");
+ fprintf(f, "\tleft: 0;\n");
+ if (css_options->withVars) {
+ fprintf(f, "\theight: var(--pixel-size);\n");
+ fprintf(f, "\twidth: var(--pixel-size);\n");
+ }
+ else {
+ fprintf(f, "\theight: %dpx;\n", css_options->pixelScale);
+ fprintf(f, "\twidth: %dpx;\n", css_options->pixelScale);
+ }
+ fprintf(f, "\tbox-shadow:\n");
+ int num_printed_pixels = 0;
+ switch (image->pixelFormat()) {
+ case IMAGE_RGB: {
+ for (y=0; yheight(); y++) {
+ for (x=0; xwidth(); x++) {
+ c = get_pixel_fast(image, x, y);
+ alpha = rgba_geta(c);
+ if (alpha != 0x00) {
+ print_shadow_color(x, y, rgba_getr(c), rgba_getg(c), rgba_getb(c),
+ alpha, num_printed_pixels>0);
+ num_printed_pixels ++;
+ }
+ }
+ fop->setProgress((float)y / (float)(image->height()));
+ }
+ break;
+ }
+ case IMAGE_GRAYSCALE: {
+ for (y=0; yheight(); y++) {
+ for (x=0; xwidth(); x++) {
+ c = get_pixel_fast(image, x, y);
+ auto v = graya_getv(c);
+ alpha = graya_geta(c);
+ if (alpha != 0x00) {
+ print_shadow_color(x, y, v, v, v, alpha, num_printed_pixels>0);
+ num_printed_pixels ++;
+ }
+ }
+ fop->setProgress((float)y / (float)(image->height()));
+ }
+ break;
+ }
+ case IMAGE_INDEXED: {
+ unsigned char image_palette[256][4];
+ for (y=0; !css_options->withVars && y<256; y++) {
+ fop->sequenceGetColor(y, &r, &g, &b);
+ image_palette[y][0] = r;
+ image_palette[y][1] = g;
+ image_palette[y][2] = b;
+ fop->sequenceGetAlpha(y, &a);
+ image_palette[y][3] = a;
+ }
+ color_t mask_color = -1;
+ if (fop->document()->sprite()->backgroundLayer() == NULL ||
+ !fop->document()->sprite()->backgroundLayer()->isVisible()) {
+ mask_color = fop->document()->sprite()->transparentColor();
+ }
+ for (y=0; yheight(); y++) {
+ for (x=0; xwidth(); x++) {
+ c = get_pixel_fast(image, x, y);
+ if (c != mask_color) {
+ if (css_options->withVars) {
+ print_shadow_index(x, y, c, num_printed_pixels>0);
+ }
+ else {
+ print_shadow_color(x, y,
+ image_palette[c][0] & 0xff,
+ image_palette[c][1] & 0xff,
+ image_palette[c][2] & 0xff,
+ image_palette[c][3] & 0xff,
+ num_printed_pixels>0);
+ }
+ num_printed_pixels ++;
+ }
+ }
+ fop->setProgress((float)y / (float)(image->height()));
+ }
+ break;
+ }
+ }
+ fprintf(f, ";\n}\n");
+ if (ferror(f)) {
+ fop->setError("Error writing file.\n");
+ return false;
+ }
+ if (css_options->generateHtml) {
+ std::string html_filepath = fop->filename() + ".html";
+ FileHandle handle(open_file_with_exception_sync_on_close(html_filepath, "wb"));
+ FILE* h = handle.get();
+ fprintf(h,
+ "",
+ fop->filename().c_str());
+ if (ferror(h)) {
+ fop->setError("Error writing html file.\n");
+ return false;
+ }
+ }
+ return true;
+}
+
+#endif
+
+// Shows the CSS configuration dialog.
+FormatOptionsPtr CssFormat::onAskUserForFormatOptions(FileOp* fop)
+{
+ auto opts = fop->formatOptionsOfDocument();
+
+#ifdef ENABLE_UI
+ if (fop->context() && fop->context()->isUIAvailable()) {
+ try {
+ auto &pref = Preferences::instance();
+
+ if (pref.isSet(pref.css.pixelScale))
+ opts->pixelScale = pref.css.pixelScale();
+
+ if (pref.isSet(pref.css.withVars))
+ opts->withVars = pref.css.withVars();
+
+ if (pref.isSet(pref.css.generateHtml))
+ opts->generateHtml = pref.css.generateHtml();
+
+ if (pref.css.showAlert()) {
+ app::gen::CssOptions win;
+ win.pixelScale()->setTextf("%d", opts->pixelScale);
+ win.withVars()->setSelected(opts->withVars);
+ win.generateHtml()->setSelected(opts->generateHtml);
+ win.openWindowInForeground();
+
+ if (win.closer() == win.ok()) {
+ pref.css.showAlert(!win.dontShow()->isSelected());
+ pref.css.pixelScale((int)win.pixelScale()->textInt());
+ pref.css.withVars(win.withVars()->isSelected());
+ pref.css.generateHtml(win.generateHtml()->isSelected());
+
+ opts->generateHtml = pref.css.generateHtml();
+ opts->withVars = pref.css.withVars();
+ opts->pixelScale = pref.css.pixelScale();
+ }
+ else {
+ opts.reset();
+ }
+ }
+ }
+ catch (std::exception &e) {
+ Console::showException(e);
+ return std::shared_ptr(nullptr);
+ }
+ }
+
+#endif
+ return opts;
+}
+
+} // namespace app
diff --git a/src/app/file/file_formats_manager.cpp b/src/app/file/file_formats_manager.cpp
index 143f5eae4..22d91ae8a 100644
--- a/src/app/file/file_formats_manager.cpp
+++ b/src/app/file/file_formats_manager.cpp
@@ -22,6 +22,7 @@ namespace app {
extern FileFormat* CreateAseFormat();
extern FileFormat* CreateBmpFormat();
+extern FileFormat* CreateCssFormat();
extern FileFormat* CreateFliFormat();
extern FileFormat* CreateGifFormat();
extern FileFormat* CreateIcoFormat();
@@ -57,6 +58,7 @@ FileFormatsManager::FileFormatsManager()
// The first format is the default image format in FileSelector
registerFormat(CreateAseFormat());
registerFormat(CreateBmpFormat());
+ registerFormat(CreateCssFormat());
registerFormat(CreateFliFormat());
registerFormat(CreateGifFormat());
registerFormat(CreateIcoFormat());
diff --git a/src/app/file/svg_format.cpp b/src/app/file/svg_format.cpp
index 9e1e19c7c..f12fb4d9a 100644
--- a/src/app/file/svg_format.cpp
+++ b/src/app/file/svg_format.cpp
@@ -73,7 +73,10 @@ FileFormat* CreateSvgFormat()
return new SvgFormat;
}
- bool SvgFormat::onLoad(FileOp* fop) { return false;}
+bool SvgFormat::onLoad(FileOp* fop)
+{
+ return false;
+}
#ifdef ENABLE_SAVE
diff --git a/src/app/modules/palettes.cpp b/src/app/modules/palettes.cpp
index f6d515851..cd79a3a1c 100644
--- a/src/app/modules/palettes.cpp
+++ b/src/app/modules/palettes.cpp
@@ -161,12 +161,7 @@ bool set_current_palette(const Palette *_palette, bool forced)
std::string get_preset_palette_filename(const std::string& preset,
const std::string& dot_extension)
{
- ResourceFinder rf;
- rf.includeUserDir(base::join_path("palettes", ".").c_str());
- std::string palettesDir = rf.getFirstOrCreateDefault();
-
- if (!base::is_directory(palettesDir))
- base::make_directory(palettesDir);
+ std::string palettesDir = get_preset_palettes_dir();
return base::join_path(palettesDir, preset + dot_extension);
}
@@ -176,4 +171,16 @@ std::string get_default_palette_preset_name()
return "default";
}
+std::string get_preset_palettes_dir()
+{
+ ResourceFinder rf;
+ rf.includeUserDir(base::join_path("palettes", ".").c_str());
+ std::string palettesDir = rf.getFirstOrCreateDefault();
+
+ if (!base::is_directory(palettesDir))
+ base::make_directory(palettesDir);
+
+ return palettesDir;
+}
+
} // namespace app
diff --git a/src/app/modules/palettes.h b/src/app/modules/palettes.h
index 551b4bedf..52f953766 100644
--- a/src/app/modules/palettes.h
+++ b/src/app/modules/palettes.h
@@ -33,6 +33,7 @@ namespace app {
std::string get_preset_palette_filename(const std::string& preset,
const std::string& dot_extension);
std::string get_default_palette_preset_name();
+ std::string get_preset_palettes_dir();
} // namespace app
diff --git a/src/app/tools/point_shapes.h b/src/app/tools/point_shapes.h
index 945f1a6cb..d8c3b93d6 100644
--- a/src/app/tools/point_shapes.h
+++ b/src/app/tools/point_shapes.h
@@ -1,5 +1,5 @@
// Aseprite
-// Copyright (C) 2019-2020 Igara Studio S.A.
+// Copyright (C) 2019-2021 Igara Studio S.A.
// Copyright (C) 2001-2017 David Capello
//
// This program is distributed under the terms of
@@ -8,6 +8,7 @@
#include "app/util/wrap_point.h"
#include "app/tools/ink.h"
+#include "doc/algorithm/flip_image.h"
#include "render/gradient.h"
namespace app {
@@ -61,7 +62,7 @@ class BrushPointShape : public PointShape {
bool m_firstPoint;
Brush* m_lastBrush;
BrushType m_origBrushType;
- std::shared_ptr m_compressedImage;
+ std::array, 4> m_compressedImages;
// For dynamics
DynamicsOptions m_dynamics;
bool m_useDynamics;
@@ -192,9 +193,7 @@ public:
// TODO cache compressed images (or remove them completelly)
if (m_lastBrush != brush) {
m_lastBrush = brush;
- m_compressedImage.reset(new CompressedImage(brush->image(),
- brush->maskBitmap(),
- false));
+ m_compressedImages.fill(nullptr);
}
x += brush->bounds().x;
@@ -227,7 +226,7 @@ public:
ink->prepareForPointShape(loop, m_firstPoint, x, y);
- for (auto scanline : *m_compressedImage) {
+ for (auto scanline : getCompressedImage(pt.symmetry)) {
int u = x+scanline.x;
ink->prepareVForPointShape(loop, y+scanline.y);
doInkHline(u, y+scanline.y, u+scanline.w-1, loop);
@@ -241,6 +240,47 @@ public:
area.y += y;
}
+private:
+ CompressedImage& getCompressedImage(gen::SymmetryMode symmetryMode) {
+ auto& compressPtr = m_compressedImages[int(symmetryMode)];
+ if (!compressPtr) {
+ switch (symmetryMode) {
+ case gen::SymmetryMode::NONE: {
+ compressPtr.reset(new CompressedImage(m_lastBrush->image(),
+ m_lastBrush->maskBitmap(),
+ false));
+ break;
+ }
+ case gen::SymmetryMode::HORIZONTAL:
+ case gen::SymmetryMode::VERTICAL: {
+ std::unique_ptr tempImage(Image::createCopy(m_lastBrush->image()));
+ doc::algorithm::FlipType flip =
+ (symmetryMode == gen::SymmetryMode::HORIZONTAL)?
+ doc::algorithm::FlipType::FlipHorizontal:
+ doc::algorithm::FlipType::FlipVertical;
+ doc::algorithm::flip_image(tempImage.get(), tempImage->bounds(), flip);
+ compressPtr.reset(new CompressedImage(tempImage.get(),
+ m_lastBrush->maskBitmap(),
+ false));
+ break;
+ }
+ case gen::SymmetryMode::BOTH: {
+ std::unique_ptr tempImage(Image::createCopy(m_lastBrush->image()));
+ doc::algorithm::flip_image(tempImage.get(),
+ tempImage->bounds(),
+ doc::algorithm::FlipType::FlipVertical);
+ doc::algorithm::flip_image(tempImage.get(),
+ tempImage->bounds(),
+ doc::algorithm::FlipType::FlipHorizontal);
+ compressPtr.reset(new CompressedImage(tempImage.get(),
+ m_lastBrush->maskBitmap(),
+ false));
+ break;
+ }
+ }
+ }
+ return *compressPtr;
+ }
};
class FloodFillPointShape : public PointShape {
diff --git a/src/app/tools/stroke.h b/src/app/tools/stroke.h
index 2b184ccb9..010edfc18 100644
--- a/src/app/tools/stroke.h
+++ b/src/app/tools/stroke.h
@@ -1,5 +1,5 @@
// Aseprite
-// Copyright (C) 2019-2020 Igara Studio S.A.
+// Copyright (C) 2019-2021 Igara Studio S.A.
// Copyright (C) 2001-2015 David Capello
//
// This program is distributed under the terms of
@@ -9,6 +9,7 @@
#define APP_TOOLS_STROKE_H_INCLUDED
#pragma once
+#include "app/pref/preferences.h"
#include "gfx/point.h"
#include "gfx/rect.h"
@@ -25,6 +26,7 @@ namespace app {
float size = 0.0f;
float angle = 0.0f;
float gradient = 0.0f;
+ gen::SymmetryMode symmetry = gen::SymmetryMode::NONE;
Pt() { }
Pt(const gfx::Point& point) : x(point.x), y(point.y) { }
Pt(int x, int y) : x(x), y(y) { }
diff --git a/src/app/tools/symmetries.cpp b/src/app/tools/symmetries.cpp
deleted file mode 100644
index edbfb7dc5..000000000
--- a/src/app/tools/symmetries.cpp
+++ /dev/null
@@ -1,84 +0,0 @@
-// Aseprite
-// Copyright (C) 2020 Igara Studio S.A.
-// Copyright (C) 2015-2017 David Capello
-//
-// This program is distributed under the terms of
-// the End-User License Agreement for Aseprite.
-
-#ifdef HAVE_CONFIG_H
-#include "config.h"
-#endif
-
-#include "app/tools/symmetries.h"
-
-#include "app/tools/point_shape.h"
-#include "app/tools/stroke.h"
-#include "app/tools/tool_loop.h"
-#include "doc/brush.h"
-
-namespace app {
-namespace tools {
-
-void HorizontalSymmetry::generateStrokes(const Stroke& mainStroke, Strokes& strokes,
- ToolLoop* loop)
-{
- int brushSize, brushCenter;
- if (loop->getPointShape()->isFloodFill()) {
- brushSize = 1;
- brushCenter = 0;
- }
- else {
- // TODO we should flip the brush center+image+bitmap or just do
- // the symmetry of all pixels
- auto brush = loop->getBrush();
- brushSize = brush->bounds().w;
- brushCenter = brush->center().x;
- }
-
- strokes.push_back(mainStroke);
-
- Stroke stroke2;
- for (const auto& pt : mainStroke) {
- Stroke::Pt pt2 = pt;
- pt2.x = m_x - ((pt.x-brushCenter) - m_x + 1) - (brushSize - brushCenter - 1);
- stroke2.addPoint(pt2);
- }
- strokes.push_back(stroke2);
-}
-
-void VerticalSymmetry::generateStrokes(const Stroke& mainStroke, Strokes& strokes,
- ToolLoop* loop)
-{
- int brushSize, brushCenter;
- if (loop->getPointShape()->isFloodFill()) {
- brushSize = 1;
- brushCenter = 0;
- }
- else {
- auto brush = loop->getBrush();
- brushSize = brush->bounds().h;
- brushCenter = brush->center().y;
- }
-
- strokes.push_back(mainStroke);
-
- Stroke stroke2;
- for (const auto& pt : mainStroke) {
- Stroke::Pt pt2 = pt;
- pt2.y = m_y - ((pt.y-brushCenter) - m_y + 1) - (brushSize - brushCenter - 1);
- stroke2.addPoint(pt2);
- }
- strokes.push_back(stroke2);
-}
-
-void SymmetryCombo::generateStrokes(const Stroke& mainStroke, Strokes& strokes,
- ToolLoop* loop)
-{
- Strokes strokes0;
- m_a->generateStrokes(mainStroke, strokes0, loop);
- for (const Stroke& stroke : strokes0)
- m_b->generateStrokes(stroke, strokes, loop);
-}
-
-} // namespace tools
-} // namespace app
diff --git a/src/app/tools/symmetries.h b/src/app/tools/symmetries.h
deleted file mode 100644
index 36d0f8004..000000000
--- a/src/app/tools/symmetries.h
+++ /dev/null
@@ -1,50 +0,0 @@
-// Aseprite
-// Copyright (C) 2015-2018 David Capello
-//
-// This program is distributed under the terms of
-// the End-User License Agreement for Aseprite.
-
-#ifndef APP_TOOLS_SYMMETRIES_H_INCLUDED
-#define APP_TOOLS_SYMMETRIES_H_INCLUDED
-#pragma once
-
-#include "app/tools/stroke.h"
-#include "app/tools/symmetry.h"
-
-#include
-
-namespace app {
-namespace tools {
-
-class HorizontalSymmetry : public Symmetry {
-public:
- HorizontalSymmetry(double x) : m_x(x) { }
- void generateStrokes(const Stroke& mainStroke, Strokes& strokes,
- ToolLoop* loop) override;
-private:
- double m_x;
-};
-
-class VerticalSymmetry : public Symmetry {
-public:
- VerticalSymmetry(double y) : m_y(y) { }
- void generateStrokes(const Stroke& mainStroke, Strokes& strokes,
- ToolLoop* loop) override;
-private:
- double m_y;
-};
-
-class SymmetryCombo : public Symmetry {
-public:
- SymmetryCombo(Symmetry* a, Symmetry* b) : m_a(a), m_b(b) { }
- void generateStrokes(const Stroke& mainStroke, Strokes& strokes,
- ToolLoop* loop) override;
-private:
- std::unique_ptr m_a;
- std::unique_ptr m_b;
-};
-
-} // namespace tools
-} // namespace app
-
-#endif
diff --git a/src/app/tools/symmetry.cpp b/src/app/tools/symmetry.cpp
new file mode 100644
index 000000000..8e6e0ecef
--- /dev/null
+++ b/src/app/tools/symmetry.cpp
@@ -0,0 +1,91 @@
+// Aseprite
+// Copyright (C) 2021 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/tools/symmetry.h"
+
+ #include "app/tools/point_shape.h"
+ #include "app/tools/tool_loop.h"
+
+namespace app {
+namespace tools {
+
+void Symmetry::generateStrokes(const Stroke& stroke, Strokes& strokes,
+ ToolLoop* loop)
+{
+ Stroke stroke2;
+ strokes.push_back(stroke);
+ gen::SymmetryMode symmetryMode = loop->getSymmetry()->mode();
+ switch (symmetryMode) {
+ case gen::SymmetryMode::NONE:
+ ASSERT(false);
+ break;
+
+ case gen::SymmetryMode::HORIZONTAL:
+ case gen::SymmetryMode::VERTICAL:
+ calculateSymmetricalStroke(stroke, stroke2, loop, symmetryMode);
+ strokes.push_back(stroke2);
+ break;
+
+ case gen::SymmetryMode::BOTH: {
+ calculateSymmetricalStroke(stroke, stroke2, loop, gen::SymmetryMode::HORIZONTAL);
+ strokes.push_back(stroke2);
+
+ Stroke stroke3;
+ calculateSymmetricalStroke(stroke, stroke3, loop, gen::SymmetryMode::VERTICAL);
+ strokes.push_back(stroke3);
+
+ Stroke stroke4;
+ calculateSymmetricalStroke(stroke3, stroke4, loop, gen::SymmetryMode::BOTH);
+ strokes.push_back(stroke4);
+ break;
+ }
+ }
+}
+
+void Symmetry::calculateSymmetricalStroke(const Stroke& refStroke, Stroke& stroke,
+ ToolLoop* loop, gen::SymmetryMode symmetryMode)
+{
+ int brushSize, brushCenter;
+ if (loop->getPointShape()->isFloodFill()) {
+ brushSize = 1;
+ brushCenter = 0;
+ }
+ else {
+ // TODO we should flip the brush center+image+bitmap or just do
+ // the symmetry of all pixels
+ auto brush = loop->getBrush();
+ if (symmetryMode == gen::SymmetryMode::HORIZONTAL || symmetryMode == gen::SymmetryMode::BOTH) {
+ brushSize = brush->bounds().w;
+ brushCenter = brush->center().x;
+ }
+ else {
+ brushSize = brush->bounds().h;
+ brushCenter = brush->center().y;
+ }
+ }
+
+ const bool isDynamic = loop->getDynamics().isDynamic();
+ for (const auto& pt : refStroke) {
+ if (isDynamic) {
+ brushSize = pt.size;
+ brushCenter = (brushSize - brushSize % 2) / 2;
+ }
+ Stroke::Pt pt2 = pt;
+ pt2.symmetry = symmetryMode;
+ if (symmetryMode == gen::SymmetryMode::HORIZONTAL || symmetryMode == gen::SymmetryMode::BOTH)
+ pt2.x = 2 * (m_x + brushCenter) - pt2.x - brushSize;
+ else
+ pt2.y = 2 * (m_y + brushCenter) - pt2.y - brushSize;
+ stroke.addPoint(pt2);
+ }
+}
+
+} // namespace tools
+} // namespace app
diff --git a/src/app/tools/symmetry.h b/src/app/tools/symmetry.h
index 5a11f7dfe..22564cac7 100644
--- a/src/app/tools/symmetry.h
+++ b/src/app/tools/symmetry.h
@@ -1,4 +1,5 @@
// Aseprite
+// Copyright (C) 2021 Igara Studio S.A.
// Copyright (C) 2015 David Capello
//
// This program is distributed under the terms of
@@ -9,24 +10,34 @@
#pragma once
#include "app/tools/stroke.h"
-
-#include
+#include "app/pref/preferences.h"
namespace app {
- namespace tools {
+namespace tools {
- class ToolLoop;
+class ToolLoop;
- // This class controls user input.
- class Symmetry {
- public:
- virtual ~Symmetry() { }
+class Symmetry {
+public:
+ Symmetry(gen::SymmetryMode symmetryMode, double x, double y)
+ : m_symmetryMode(symmetryMode)
+ , m_x(x)
+ , m_y(y) {
+ }
- // The "stroke" must be relative to the sprite origin.
- virtual void generateStrokes(const Stroke& stroke, Strokes& strokes, ToolLoop* loop) = 0;
- };
+ void generateStrokes(const Stroke& stroke, Strokes& strokes, ToolLoop* loop);
- } // namespace tools
+ gen::SymmetryMode mode() const { return m_symmetryMode; }
+
+private:
+ void calculateSymmetricalStroke(const Stroke& refStroke, Stroke& stroke,
+ ToolLoop* loop, gen::SymmetryMode symmetryMode);
+
+ gen::SymmetryMode m_symmetryMode;
+ double m_x, m_y;
+};
+
+} // namespace tools
} // namespace app
#endif
diff --git a/src/app/ui/browser_view.cpp b/src/app/ui/browser_view.cpp
index 616ae5e28..78db1e8ca 100644
--- a/src/app/ui/browser_view.cpp
+++ b/src/app/ui/browser_view.cpp
@@ -1,5 +1,5 @@
// Aseprite
-// Copyright (C) 2018-2019 Igara Studio S.A.
+// Copyright (C) 2018-2021 Igara Studio S.A.
// Copyright (C) 2016-2017 David Capello
//
// This program is distributed under the terms of
@@ -16,6 +16,7 @@
#include "app/ui/main_window.h"
#include "app/ui/separator_in_view.h"
#include "app/ui/skin/skin_theme.h"
+#include "app/ui/status_bar.h"
#include "app/ui/workspace.h"
#include "base/file_handle.h"
#include "base/fs.h"
@@ -572,7 +573,8 @@ WorkspaceView* BrowserView::cloneWorkspaceView()
void BrowserView::onWorkspaceViewSelected()
{
- // Do nothing
+ if (auto statusBar = StatusBar::instance())
+ statusBar->clearText();
}
bool BrowserView::onCloseView(Workspace* workspace, bool quitting)
diff --git a/src/app/ui/data_recovery_view.cpp b/src/app/ui/data_recovery_view.cpp
index 72122a129..2caf40e11 100644
--- a/src/app/ui/data_recovery_view.cpp
+++ b/src/app/ui/data_recovery_view.cpp
@@ -1,5 +1,5 @@
// Aseprite
-// Copyright (C) 2019-2020 Igara Studio S.A.
+// Copyright (C) 2019-2021 Igara Studio S.A.
// Copyright (C) 2001-2017 David Capello
//
// This program is distributed under the terms of
@@ -13,6 +13,7 @@
#include "app/app.h"
#include "app/app_menus.h"
+#include "app/console.h"
#include "app/crash/data_recovery.h"
#include "app/crash/session.h"
#include "app/doc.h"
@@ -106,19 +107,31 @@ public:
m_task = new TaskWidget(
TaskWidget::kCannotCancel,
- [this](base::task_token& t){
- // Warning: This is executed from a worker thread
- m_session->deleteBackup(m_backup);
- ui::execute_from_ui_thread(
- [this]{
- onDeleteTaskWidget();
+ [this](base::task_token& t) {
+ try {
+ // Warning: This is executed from a worker thread
+ m_session->deleteBackup(m_backup);
- // We cannot use this->deferDelete() here because it looks
- // like the m_task field can be still in use.
- setVisible(false);
+ ui::execute_from_ui_thread(
+ [this]{
+ onDeleteTaskWidget();
- updateView();
- });
+ // We cannot use this->deferDelete() here because it looks
+ // like the m_task field can be still in use.
+ setVisible(false);
+
+ updateView();
+ });
+ }
+ catch (const std::exception& ex) {
+ std::string err = ex.what();
+ if (!err.empty()) {
+ ui::execute_from_ui_thread(
+ [err]{
+ Console().printf("Error deleting file: %s", err.c_str());
+ });
+ }
+ }
});
addChild(m_task);
updateView();
@@ -477,6 +490,7 @@ void DataRecoveryView::onDelete()
int(items.size()))) != 1)
return; // Cancel
+ Console console;
for (auto item : items)
item->deleteBackup();
}
diff --git a/src/app/ui/doc_view.cpp b/src/app/ui/doc_view.cpp
index e13eefb2f..cb1962e03 100644
--- a/src/app/ui/doc_view.cpp
+++ b/src/app/ui/doc_view.cpp
@@ -1,5 +1,5 @@
// Aseprite
-// Copyright (C) 2018-2020 Igara Studio S.A.
+// Copyright (C) 2018-2021 Igara Studio S.A.
// Copyright (C) 2001-2018 David Capello
//
// This program is distributed under the terms of
@@ -248,7 +248,8 @@ WorkspaceView* DocView::cloneWorkspaceView()
void DocView::onWorkspaceViewSelected()
{
- StatusBar::instance()->showDefaultText(m_document);
+ if (auto statusBar = StatusBar::instance())
+ statusBar->showDefaultText(m_document);
}
void DocView::onClonedFrom(WorkspaceView* from)
diff --git a/src/app/ui/editor/tool_loop_impl.cpp b/src/app/ui/editor/tool_loop_impl.cpp
index 19daa61b0..9aa41b10f 100644
--- a/src/app/ui/editor/tool_loop_impl.cpp
+++ b/src/app/ui/editor/tool_loop_impl.cpp
@@ -1,5 +1,5 @@
// Aseprite
-// Copyright (C) 2019-2020 Igara Studio S.A.
+// Copyright (C) 2019-2021 Igara Studio S.A.
// Copyright (C) 2001-2018 David Capello
//
// This program is distributed under the terms of
@@ -29,7 +29,7 @@
#include "app/tools/freehand_algorithm.h"
#include "app/tools/ink.h"
#include "app/tools/point_shape.h"
-#include "app/tools/symmetries.h"
+#include "app/tools/symmetry.h"
#include "app/tools/tool.h"
#include "app/tools/tool_box.h"
#include "app/tools/tool_loop.h"
@@ -239,27 +239,10 @@ public:
// Symmetry mode
if (Preferences::instance().symmetryMode.enabled()) {
- switch (m_docPref.symmetry.mode()) {
-
- case app::gen::SymmetryMode::NONE:
- ASSERT(m_symmetry == nullptr);
- break;
-
- case app::gen::SymmetryMode::HORIZONTAL:
- m_symmetry.reset(new app::tools::HorizontalSymmetry(m_docPref.symmetry.xAxis()));
- break;
-
- case app::gen::SymmetryMode::VERTICAL:
- m_symmetry.reset(new app::tools::VerticalSymmetry(m_docPref.symmetry.yAxis()));
- break;
-
- case app::gen::SymmetryMode::BOTH:
- m_symmetry.reset(
- new app::tools::SymmetryCombo(
- new app::tools::HorizontalSymmetry(m_docPref.symmetry.xAxis()),
- new app::tools::VerticalSymmetry(m_docPref.symmetry.yAxis())));
- break;
- }
+ if (m_docPref.symmetry.mode() != gen::SymmetryMode::NONE)
+ m_symmetry.reset(new tools::Symmetry(m_docPref.symmetry.mode(),
+ m_docPref.symmetry.xAxis(),
+ m_docPref.symmetry.yAxis()));
}
// Ignore opacity for these inks
diff --git a/src/app/ui/main_window.cpp b/src/app/ui/main_window.cpp
index fca7d4eb5..a12cb6943 100644
--- a/src/app/ui/main_window.cpp
+++ b/src/app/ui/main_window.cpp
@@ -1,5 +1,5 @@
// Aseprite
-// Copyright (C) 2018-2020 Igara Studio S.A.
+// Copyright (C) 2018-2021 Igara Studio S.A.
// Copyright (C) 2001-2018 David Capello
//
// This program is distributed under the terms of
@@ -261,9 +261,19 @@ void MainWindow::showHome()
m_tabsBar->selectTab(m_homeView);
}
-bool MainWindow::isHomeSelected()
+void MainWindow::showDefaultStatusBar()
{
- return (m_tabsBar->getSelectedTab() == m_homeView && m_homeView);
+ if (DocView* docView = getDocView())
+ m_statusBar->showDefaultText(docView->document());
+ else if (isHomeSelected())
+ m_statusBar->showAbout();
+ else
+ m_statusBar->clearText();
+}
+
+bool MainWindow::isHomeSelected() const
+{
+ return (m_homeView && m_workspace->activeView() == m_homeView);
}
void MainWindow::showBrowser(const std::string& filename)
@@ -473,12 +483,12 @@ void MainWindow::onTabsContainerDoubleClicked(Tabs* tabs)
void MainWindow::onMouseOverTab(Tabs* tabs, TabView* tabView)
{
// Note: tabView can be NULL
- if (DocView* docView = dynamic_cast(tabView)) {
+ if (DocView* docView = dynamic_cast(tabView))
m_statusBar->showDefaultText(docView->document());
- }
- else {
+ else if (tabView)
+ m_statusBar->setStatusText(0, tabView->getTabText());
+ else
m_statusBar->showDefaultText();
- }
}
void MainWindow::onMouseLeaveTab()
diff --git a/src/app/ui/main_window.h b/src/app/ui/main_window.h
index dd2b1d70b..e518fa239 100644
--- a/src/app/ui/main_window.h
+++ b/src/app/ui/main_window.h
@@ -1,5 +1,5 @@
// Aseprite
-// Copyright (C) 2018-2019 Igara Studio S.A.
+// Copyright (C) 2018-2021 Igara Studio S.A.
// Copyright (C) 2001-2018 David Capello
//
// This program is distributed under the terms of
@@ -71,9 +71,10 @@ namespace app {
void showNotification(INotificationDelegate* del);
void showHomeOnOpen();
void showHome();
- bool isHomeSelected();
+ void showDefaultStatusBar();
void showDevConsole();
void showBrowser(const std::string& filename);
+ bool isHomeSelected() const;
Mode getMode() const { return m_mode; }
void setMode(Mode mode);
diff --git a/src/app/ui/palette_popup.cpp b/src/app/ui/palette_popup.cpp
index a6467aed6..39a831cfe 100644
--- a/src/app/ui/palette_popup.cpp
+++ b/src/app/ui/palette_popup.cpp
@@ -42,6 +42,7 @@ PalettePopup::PalettePopup()
addChild(m_popup);
m_paletteListBox.DoubleClickItem.connect([this]{ onLoadPal(); });
+ m_paletteListBox.FinishLoading.connect([this]{ onSearchChange(); });
m_popup->search()->Change.connect([this]{ onSearchChange(); });
m_popup->loadPal()->Click.connect([this]{ onLoadPal(); });
m_popup->openFolder()->Click.connect([this]{ onOpenFolder(); });
diff --git a/src/app/ui/palettes_listbox.cpp b/src/app/ui/palettes_listbox.cpp
index e7ae354cc..049fd3911 100644
--- a/src/app/ui/palettes_listbox.cpp
+++ b/src/app/ui/palettes_listbox.cpp
@@ -124,6 +124,9 @@ PalettesListBox::PalettesListBox()
m_extPaletteChanges =
App::instance()->extensions().PalettesChange.connect(
[this]{ reload(); });
+ m_extPresetsChanges =
+ App::instance()->PalettePresetsChange.connect(
+ [this]{ reload(); });
}
doc::Palette* PalettesListBox::selectedPalette()
diff --git a/src/app/ui/palettes_listbox.h b/src/app/ui/palettes_listbox.h
index 57af50fa5..26247148b 100644
--- a/src/app/ui/palettes_listbox.h
+++ b/src/app/ui/palettes_listbox.h
@@ -34,6 +34,7 @@ namespace app {
ui::TooltipManager m_tooltips;
obs::scoped_connection m_extPaletteChanges;
+ obs::scoped_connection m_extPresetsChanges;
};
} // namespace app
diff --git a/src/app/ui/resources_listbox.cpp b/src/app/ui/resources_listbox.cpp
index 12683591a..fe4717bc0 100644
--- a/src/app/ui/resources_listbox.cpp
+++ b/src/app/ui/resources_listbox.cpp
@@ -211,8 +211,10 @@ void ResourcesListBox::onTick()
listItem.release();
}
- if (m_resourcesLoader->isDone())
+ if (m_resourcesLoader->isDone()) {
+ FinishLoading();
stop();
+ }
}
void ResourcesListBox::stop()
diff --git a/src/app/ui/resources_listbox.h b/src/app/ui/resources_listbox.h
index aa8a38d61..418e705ca 100644
--- a/src/app/ui/resources_listbox.h
+++ b/src/app/ui/resources_listbox.h
@@ -9,6 +9,7 @@
#pragma once
#include "app/res/resources_loader.h"
+#include "obs/signal.h"
#include "ui/listbox.h"
#include "ui/listitem.h"
#include "ui/timer.h"
@@ -43,6 +44,8 @@ class ResourceListItem : public ui::ListItem {
void reload();
+ obs::signal FinishLoading;
+
protected:
virtual bool onProcessMessage(ui::Message* msg) override;
virtual void onChange() override;
diff --git a/src/app/ui/status_bar.cpp b/src/app/ui/status_bar.cpp
index 239e1d057..7e4745af5 100644
--- a/src/app/ui/status_bar.cpp
+++ b/src/app/ui/status_bar.cpp
@@ -1,5 +1,5 @@
// Aseprite
-// Copyright (C) 2018-2020 Igara Studio S.A.
+// Copyright (C) 2018-2021 Igara Studio S.A.
// Copyright (C) 2001-2018 David Capello
//
// This program is distributed under the terms of
@@ -61,6 +61,40 @@ using namespace gfx;
using namespace ui;
using namespace doc;
+class StatusBar::AboutStatusBar : public HBox {
+public:
+ AboutStatusBar()
+ : m_label(fmt::format("{} {} by ", get_app_name(), get_app_version()))
+ , m_link("", "Igara Studio")
+ {
+ m_link.Click.connect(
+ []{
+ Command* cmd = Commands::instance()->byId(CommandId::About());
+ UIContext::instance()->executeCommandFromMenuOrShortcut(cmd);
+ });
+
+ addChild(new BoxFiller);
+ addChild(&m_label);
+ addChild(&m_link);
+ addChild(new BoxFiller);
+
+ InitTheme.connect(
+ [this]{
+ SkinTheme* theme = static_cast(this->theme());
+ ui::Style* style = theme->styles.workspaceLink();
+ noBorderNoChildSpacing();
+ m_label.setStyle(style);
+ m_link.setStyle(style);
+ m_label.noBorderNoChildSpacing();
+ m_link.noBorderNoChildSpacing();
+ });
+ initTheme();
+ }
+
+ ui::Label m_label;
+ ui::LinkLabel m_link;
+};
+
class StatusBar::Indicators : public HBox {
class Indicator : public Widget {
@@ -632,6 +666,7 @@ StatusBar* StatusBar::m_instance = NULL;
StatusBar::StatusBar(TooltipManager* tooltipManager)
: m_timeout(0)
+ , m_about(new AboutStatusBar)
, m_indicators(new Indicators)
, m_docControls(new HBox)
, m_tipwindow(nullptr)
@@ -642,8 +677,12 @@ StatusBar::StatusBar(TooltipManager* tooltipManager)
setDoubleBuffered(true);
setFocusStop(true);
+ m_about->setExpansive(true);
+ m_about->setVisible(true);
m_indicators->setExpansive(true);
+ m_indicators->setVisible(false);
m_docControls->setVisible(false);
+ addChild(m_about);
addChild(m_indicators);
addChild(m_docControls);
@@ -698,6 +737,7 @@ void StatusBar::onSelectedToolChange(tools::Tool* tool)
void StatusBar::clearText()
{
+ showIndicators();
setStatusText(1, std::string());
}
@@ -706,16 +746,11 @@ void StatusBar::clearText()
// details of the main window/docs/etc.
void StatusBar::showDefaultText()
{
- if (current_editor) {
- showDefaultText(current_editor->document());
- }
- else if (App::instance()->mainWindow()->isHomeSelected()) {
- setStatusText(0, fmt::format("-- {} {} by David & Gaspar Capello -- Igara Studio --",
- get_app_name(), get_app_version()));
- }
- else {
+ auto mainWindow = (App::instance() ? App::instance()->mainWindow(): nullptr);
+ if (mainWindow)
+ mainWindow->showDefaultStatusBar();
+ else
clearText();
- }
}
void StatusBar::showDefaultText(Doc* doc)
@@ -742,18 +777,22 @@ void StatusBar::showDefaultText(Doc* doc)
void StatusBar::updateFromEditor(Editor* editor)
{
- if (editor)
+ if (editor) {
+ showIndicators();
m_zoomEntry->setZoom(editor->zoom());
+ }
}
void StatusBar::showBackupIcon(BackupIcon icon)
{
+ showIndicators();
m_indicators->showBackupIcon(icon);
}
bool StatusBar::setStatusText(int msecs, const std::string& msg)
{
if ((base::current_tick() > m_timeout) || (msecs > 0)) {
+ showIndicators();
IndicatorsGeneration(m_indicators).add(msg.c_str());
m_timeout = base::current_tick() + msecs;
return true;
@@ -795,6 +834,7 @@ void StatusBar::showTip(int msecs, const std::string& msg)
void StatusBar::showColor(int msecs, const char* text, const app::Color& color)
{
if ((base::current_tick() > m_timeout) || (msecs > 0)) {
+ showIndicators();
IndicatorsGeneration gen(m_indicators);
gen.add(color);
if (text)
@@ -818,7 +858,9 @@ void StatusBar::showTile(int msecs, const char* text, doc::tile_t tile)
void StatusBar::showTool(int msecs, tools::Tool* tool)
{
- ASSERT(tool != NULL);
+ showIndicators();
+
+ ASSERT(tool != nullptr);
IndicatorsGeneration(m_indicators).add(tool);
m_timeout = base::current_tick() + msecs;
@@ -946,4 +988,22 @@ void StatusBar::updateSnapToGridWindowPosition()
rc.y-m_snapToGridWindow->bounds().h);
}
+void StatusBar::showAbout()
+{
+ if (!m_about->isVisible()) {
+ m_indicators->setVisible(false);
+ m_about->setVisible(true);
+ m_about->layout();
+ }
+}
+
+void StatusBar::showIndicators()
+{
+ if (!m_indicators->isVisible()) {
+ m_about->setVisible(false);
+ m_indicators->setVisible(true);
+ m_indicators->layout();
+ }
+}
+
} // namespace app
diff --git a/src/app/ui/status_bar.h b/src/app/ui/status_bar.h
index 41c4d6877..3ffffa4d2 100644
--- a/src/app/ui/status_bar.h
+++ b/src/app/ui/status_bar.h
@@ -1,5 +1,5 @@
// Aseprite
-// Copyright (C) 2018-2020 Igara Studio S.A.
+// Copyright (C) 2018-2021 Igara Studio S.A.
// Copyright (C) 2001-2018 David Capello
//
// This program is distributed under the terms of
@@ -56,7 +56,8 @@ namespace app {
void clearText();
void showDefaultText();
- void showDefaultText(Doc* document);
+ void showDefaultText(Doc* doc);
+ void showAbout();
bool setStatusText(int msecs, const std::string& text);
void showTip(int msecs, const std::string& msg);
@@ -88,9 +89,14 @@ namespace app {
void newFrame();
void onChangeZoom(const render::Zoom& zoom);
void updateSnapToGridWindowPosition();
+ void showIndicators();
base::tick_t m_timeout;
+ // About text
+ class AboutStatusBar;
+ AboutStatusBar* m_about;
+
// Indicators
class Indicators;
class IndicatorsGeneration;
diff --git a/src/app/ui_context.cpp b/src/app/ui_context.cpp
index e9f0110aa..c1a028d10 100644
--- a/src/app/ui_context.cpp
+++ b/src/app/ui_context.cpp
@@ -1,5 +1,5 @@
// Aseprite
-// Copyright (C) 2019-2020 Igara Studio S.A.
+// Copyright (C) 2019-2021 Igara Studio S.A.
// Copyright (C) 2001-2018 David Capello
//
// This program is distributed under the terms of
@@ -100,16 +100,17 @@ void UIContext::setActiveView(DocView* docView)
return;
if (docView) {
+ current_editor = docView->editor();
mainWin->getTabsBar()->selectTab(docView);
if (mainWin->getWorkspace()->activeView() != docView)
mainWin->getWorkspace()->setActiveView(docView);
+
+ if (current_editor)
+ current_editor->requestFocus();
}
-
- current_editor = (docView ? docView->editor(): nullptr);
-
- if (current_editor)
- current_editor->requestFocus();
+ else
+ current_editor = nullptr;
mainWin->getPreviewEditor()->updateUsingEditor(current_editor);
mainWin->getTimeline()->updateUsingEditor(current_editor);
diff --git a/src/dio/detect_format.cpp b/src/dio/detect_format.cpp
index 2c975d779..65ed992ed 100644
--- a/src/dio/detect_format.cpp
+++ b/src/dio/detect_format.cpp
@@ -141,6 +141,9 @@ FileFormat detect_format_by_file_extension(const std::string& filename)
if (ext == "tga")
return FileFormat::TARGA_IMAGE;
+ if (ext == "css")
+ return FileFormat::CSS_STYLE;
+
if (ext == "webp")
return FileFormat::WEBP_ANIMATION;
diff --git a/src/dio/file_format.h b/src/dio/file_format.h
index 04baaea25..b8598644c 100644
--- a/src/dio/file_format.h
+++ b/src/dio/file_format.h
@@ -31,6 +31,7 @@ enum class FileFormat {
SVG_IMAGE,
TARGA_IMAGE,
WEBP_ANIMATION,
+ CSS_STYLE,
};
} // namespace dio
diff --git a/src/doc/keyframes.h b/src/doc/keyframes.h
index e6c80a896..cb0c980db 100644
--- a/src/doc/keyframes.h
+++ b/src/doc/keyframes.h
@@ -8,7 +8,6 @@
#define DOC_KEYFRAMES_H_INCLUDED
#pragma once
-#include "base/disable_copying.h"
#include "doc/frame.h"
#include
@@ -111,6 +110,11 @@ namespace doc {
Keyframes() { }
+ Keyframes(const Keyframes& other) {
+ for (const auto& key : other.m_keys)
+ m_keys.push_back(Key(key.frame(), new T(*key.value())));
+ }
+
void insert(const frame_t frame, T* value) {
auto it = getIterator(frame);
if (it == end())
@@ -189,9 +193,6 @@ namespace doc {
private:
List m_keys;
-
- // Disable operator=
- DISABLE_COPYING(Keyframes);
};
} // namespace doc
diff --git a/src/doc/slice.cpp b/src/doc/slice.cpp
index f20c2f2c5..28bbf9e1a 100644
--- a/src/doc/slice.cpp
+++ b/src/doc/slice.cpp
@@ -42,6 +42,14 @@ Slice::Slice()
{
}
+Slice::Slice(const Slice& other)
+ : WithUserData(other)
+ , m_owner(nullptr)
+ , m_name(other.m_name)
+ , m_keys(other.m_keys)
+{
+}
+
Slice::~Slice()
{
ASSERT(!m_owner);
diff --git a/src/doc/slice.h b/src/doc/slice.h
index e337d8df2..b9fa5ba48 100644
--- a/src/doc/slice.h
+++ b/src/doc/slice.h
@@ -55,6 +55,7 @@ namespace doc {
typedef List::const_iterator const_iterator;
Slice();
+ Slice(const Slice& other);
~Slice();
int getMemSize() const override;
@@ -90,8 +91,6 @@ namespace doc {
Slices* m_owner;
std::string m_name;
List m_keys;
-
- DISABLE_COPYING(Slice);
};
} // namespace doc
diff --git a/src/main/resources_win32.rc b/src/main/resources_win32.rc
index 0af9c10a9..a1fc3c879 100644
--- a/src/main/resources_win32.rc
+++ b/src/main/resources_win32.rc
@@ -23,7 +23,7 @@ BEGIN
VALUE "FileDescription", "Aseprite - Animated sprites editor & pixel art tool"
VALUE "FileVersion", "1,3,0,0"
VALUE "InternalName", "aseprite"
- VALUE "LegalCopyright", "Copyright (C) 2001-2020 Igara Studio S.A."
+ VALUE "LegalCopyright", "Copyright (C) 2001-2021 Igara Studio S.A."
VALUE "OriginalFilename", "aseprite.exe"
VALUE "ProductName", "ASEPRITE"
VALUE "ProductVersion", "1,3,0,0"
diff --git a/src/ui/widget.cpp b/src/ui/widget.cpp
index 5a7ecc069..5549ff0a7 100644
--- a/src/ui/widget.cpp
+++ b/src/ui/widget.cpp
@@ -1,5 +1,5 @@
// Aseprite UI Library
-// Copyright (C) 2018-2020 Igara Studio S.A.
+// Copyright (C) 2018-2021 Igara Studio S.A.
// Copyright (C) 2001-2018 David Capello
//
// This file is released under the terms of the MIT license.
@@ -106,7 +106,11 @@ Widget::~Widget()
void Widget::deferDelete()
{
- manager()->addToGarbage(this);
+ if (auto man = manager())
+ man->addToGarbage(this);
+ else {
+ ASSERT(false);
+ }
}
void Widget::initTheme()
@@ -219,7 +223,8 @@ void Widget::setVisible(bool state)
}
else {
if (!hasFlags(HIDDEN)) {
- manager()->freeWidget(this); // Free from manager
+ if (auto man = manager())
+ man->freeWidget(this); // Free from manager
enableFlags(HIDDEN);
onVisible(false);
@@ -241,7 +246,8 @@ void Widget::setEnabled(bool state)
}
else {
if (!hasFlags(DISABLED)) {
- manager()->freeWidget(this); // Free from the manager
+ if (auto man = manager())
+ man->freeWidget(this); // Free from the manager
enableFlags(DISABLED);
invalidate();
@@ -541,9 +547,8 @@ void Widget::removeChild(WidgetsList::iterator& it)
m_children.erase(it);
// Free from manager
- Manager* manager = this->manager();
- if (manager)
- manager->freeWidget(child);
+ if (auto man = manager())
+ man->freeWidget(child);
child->m_parent = nullptr;
}
@@ -617,7 +622,8 @@ void Widget::layout()
void Widget::loadLayout()
{
if (!m_id.empty()) {
- LayoutIO* io = manager()->getLayoutIO();
+ auto man = manager();
+ LayoutIO* io = (man ? man->getLayoutIO(): nullptr);
if (io) {
std::string layout = io->loadLayout(this);
if (!layout.empty()) {
@@ -636,7 +642,8 @@ void Widget::loadLayout()
void Widget::saveLayout()
{
if (!m_id.empty()) {
- LayoutIO* io = manager()->getLayoutIO();
+ auto man = manager();
+ LayoutIO* io = (man ? man->getLayoutIO(): nullptr);
if (io) {
std::stringstream s;
SaveLayoutEvent ev(this, s);
@@ -690,8 +697,8 @@ void Widget::setBoundsQuietly(const gfx::Rect& rc)
m_bounds = rc;
// Remove all paint messages for this widget.
- if (Manager* manager = this->manager())
- manager->removeMessagesFor(this, kPaintMessage);
+ if (Manager* man = manager())
+ man->removeMessagesFor(this, kPaintMessage);
}
// TODO Test moving this inside the if (m_bounds != rc) { ... }
@@ -975,6 +982,8 @@ void Widget::flushRedraw()
Manager* manager = this->manager();
ASSERT(manager);
+ if (!manager)
+ return;
while (!processing.empty()) {
Widget* widget = processing.front();
@@ -1300,36 +1309,44 @@ void Widget::resetSizeHint()
void Widget::requestFocus()
{
- manager()->setFocus(this);
+ if (auto man = manager())
+ man->setFocus(this);
}
void Widget::releaseFocus()
{
- if (hasFocus())
- manager()->freeFocus();
+ if (hasFocus()) {
+ if (auto man = manager())
+ man->freeFocus();
+ }
}
// Captures the mouse to send all the future mouse messsages to the
// specified widget (included the kMouseMoveMessage and kSetCursorMessage).
void Widget::captureMouse()
{
- if (!manager()->getCapture()) {
- manager()->setCapture(this);
+ if (auto man = manager()) {
+ if (!man->getCapture()) {
+ man->setCapture(this);
+ }
}
}
// Releases the capture of the mouse events.
void Widget::releaseMouse()
{
- if (manager()->getCapture() == this) {
- manager()->freeCapture();
+ if (auto man = manager()) {
+ if (man->getCapture() == this) {
+ man->freeCapture();
+ }
}
}
bool Widget::offerCapture(ui::MouseMessage* mouseMsg, int widget_type)
{
if (hasCapture()) {
- Widget* pick = manager()->pick(mouseMsg->position());
+ auto man = manager();
+ Widget* pick = (man ? man->pick(mouseMsg->position()): nullptr);
if (pick && pick != this && pick->type() == widget_type) {
releaseMouse();
@@ -1340,7 +1357,7 @@ bool Widget::offerCapture(ui::MouseMessage* mouseMsg, int widget_type)
mouseMsg->modifiers(),
mouseMsg->position());
mouseMsg2->setRecipient(pick);
- manager()->enqueueMessage(mouseMsg2);
+ man->enqueueMessage(mouseMsg2);
return true;
}
}
@@ -1601,8 +1618,8 @@ void Widget::offsetWidgets(int dx, int dy)
m_bounds.offset(dx, dy);
// Remove all paint messages for this widget.
- if (Manager* manager = this->manager())
- manager->removeMessagesFor(this, kPaintMessage);
+ if (auto man = manager())
+ man->removeMessagesFor(this, kPaintMessage);
for (auto child : m_children)
child->offsetWidgets(dx, dy);