mirror of
https://github.com/aseprite/aseprite.git
synced 2025-03-18 07:21:09 +00:00
Merge branch 'main' into beta
This commit is contained in:
commit
f62b5eafb1
32
.pre-commit-config.yaml
Normal file
32
.pre-commit-config.yaml
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
exclude: 'third_party/.*|laf/.*'
|
||||||
|
|
||||||
|
repos:
|
||||||
|
- repo: https://github.com/pre-commit/pre-commit-hooks
|
||||||
|
rev: v5.0.0
|
||||||
|
hooks:
|
||||||
|
- id: end-of-file-fixer
|
||||||
|
- id: trailing-whitespace
|
||||||
|
|
||||||
|
- repo: https://github.com/Lucas-C/pre-commit-hooks
|
||||||
|
rev: v1.5.5
|
||||||
|
hooks:
|
||||||
|
- id: remove-tabs
|
||||||
|
args: [--whitespaces-count, '8']
|
||||||
|
|
||||||
|
- repo: https://github.com/pre-commit/mirrors-clang-format
|
||||||
|
rev: v19.1.5
|
||||||
|
hooks:
|
||||||
|
- id: clang-format
|
||||||
|
files: \.(c|h|cpp|hpp|cc|hh|cxx|hxx)$
|
||||||
|
types_or: [text]
|
||||||
|
|
||||||
|
- repo: https://github.com/pocc/pre-commit-hooks
|
||||||
|
rev: v1.3.5
|
||||||
|
hooks:
|
||||||
|
- id: clang-tidy
|
||||||
|
files: \.(c|h|cpp|hpp|cc|hh|cxx|hxx)$
|
||||||
|
args: [--fix, --quiet, --use-color]
|
||||||
|
types_or: [text]
|
||||||
|
additional_dependencies: [clang-tidy==19.1.0]
|
||||||
|
require_serial: true
|
||||||
|
stages: [manual]
|
@ -16,7 +16,7 @@ Before you submit an issue:
|
|||||||
click the `Subscribe` or `Watching` button to get notifications
|
click the `Subscribe` or `Watching` button to get notifications
|
||||||
via email.
|
via email.
|
||||||
|
|
||||||
# Compilation problem
|
# Compilation problems
|
||||||
|
|
||||||
Before you submit an issue or a post about a **compilation problem**,
|
Before you submit an issue or a post about a **compilation problem**,
|
||||||
check the following items:
|
check the following items:
|
||||||
@ -77,6 +77,20 @@ new [features](https://community.aseprite.org/c/features),
|
|||||||
[bug reports](https://community.aseprite.org/c/bugs), etc. You are
|
[bug reports](https://community.aseprite.org/c/bugs), etc. You are
|
||||||
encouraged to create mockups for any issue you see and attach them.
|
encouraged to create mockups for any issue you see and attach them.
|
||||||
|
|
||||||
|
## Pre-commit hooks
|
||||||
|
|
||||||
|
We use [pre-commit](https://pre-commit.com/) as way to set up hooks, you can install it with:
|
||||||
|
|
||||||
|
```
|
||||||
|
pip install pre-commit
|
||||||
|
pre-commit install
|
||||||
|
```
|
||||||
|
|
||||||
|
If you need to run it manually, use `pre-commit run`.
|
||||||
|
|
||||||
|
To run `clang-tidy`, you can use `pre-commit run --hook-stage manual clang-tidy`.
|
||||||
|
Make sure to check the suggestions and to not apply them arbitrarily, since some might not be 100% applicable to what you're doing.
|
||||||
|
|
||||||
## Code submission policy
|
## Code submission policy
|
||||||
|
|
||||||
We have some rules for the changes and commits that are contributed:
|
We have some rules for the changes and commits that are contributed:
|
||||||
|
@ -59,7 +59,7 @@ To compile Aseprite you will need:
|
|||||||
|
|
||||||
## Windows dependencies
|
## Windows dependencies
|
||||||
|
|
||||||
* Windows 10 (we don't support cross-compiling)
|
* Windows 10/11 (we don't support cross-compiling)
|
||||||
* [Visual Studio Community 2022](https://visualstudio.microsoft.com/downloads/) (we don't support [MinGW](#mingw))
|
* [Visual Studio Community 2022](https://visualstudio.microsoft.com/downloads/) (we don't support [MinGW](#mingw))
|
||||||
* The [Desktop development with C++ item + Windows 10.0.18362.0 SDK](https://imgur.com/a/7zs51IT)
|
* The [Desktop development with C++ item + Windows 10.0.18362.0 SDK](https://imgur.com/a/7zs51IT)
|
||||||
from the Visual Studio installer
|
from the Visual Studio installer
|
||||||
|
@ -859,6 +859,8 @@ x = X:
|
|||||||
y = Y:
|
y = Y:
|
||||||
width = Width:
|
width = Width:
|
||||||
height = Height:
|
height = Height:
|
||||||
|
columns = Columns:
|
||||||
|
rows = Rows:
|
||||||
padding = Padding
|
padding = Padding
|
||||||
horizontal_padding = Horizontal:
|
horizontal_padding = Horizontal:
|
||||||
vertical_padding = Vertical:
|
vertical_padding = Vertical:
|
||||||
|
@ -24,6 +24,14 @@
|
|||||||
<label text="@.height" />
|
<label text="@.height" />
|
||||||
<expr id="height" text="16" />
|
<expr id="height" text="16" />
|
||||||
|
|
||||||
|
<separator horizontal="true" cell_hspan="4" />
|
||||||
|
|
||||||
|
<label text="@.columns" />
|
||||||
|
<expr id="columns" />
|
||||||
|
|
||||||
|
<label text="@.rows" />
|
||||||
|
<expr id="rows" />
|
||||||
|
|
||||||
<check id="padding_enabled" text="@.padding" cell_hspan="4" />
|
<check id="padding_enabled" text="@.padding" cell_hspan="4" />
|
||||||
|
|
||||||
<label text="@.horizontal_padding" id="horizontal_padding_label" />
|
<label text="@.horizontal_padding" id="horizontal_padding_label" />
|
||||||
|
2
laf
2
laf
@ -1 +1 @@
|
|||||||
Subproject commit 4766fd95801af52b904146e79e5d89a916de49de
|
Subproject commit 3eee03a1f3a3ce7b06e93b1106fc356bc0307c42
|
@ -44,18 +44,41 @@ namespace app {
|
|||||||
|
|
||||||
using namespace ui;
|
using namespace ui;
|
||||||
|
|
||||||
|
gfx::Size calcFrameSize(gfx::Size availSize,
|
||||||
|
int cols, int rows,
|
||||||
|
const gfx::Point& frameOrigin,
|
||||||
|
const gfx::Size& padding)
|
||||||
|
{
|
||||||
|
if (cols <= 0)
|
||||||
|
cols = 1;
|
||||||
|
if (rows <= 0)
|
||||||
|
rows = 1;
|
||||||
|
|
||||||
|
availSize.w -= (frameOrigin.x + padding.w*(cols-1));
|
||||||
|
availSize.h -= (frameOrigin.y + padding.h*(rows-1));
|
||||||
|
|
||||||
|
return gfx::Size(availSize.w / cols, availSize.h / rows);
|
||||||
|
}
|
||||||
|
|
||||||
struct ImportSpriteSheetParams : public NewParams {
|
struct ImportSpriteSheetParams : public NewParams {
|
||||||
Param<bool> ui { this, true, "ui" };
|
Param<bool> ui { this, true, "ui" };
|
||||||
Param<app::SpriteSheetType> type { this, app::SpriteSheetType::None, "type" };
|
Param<app::SpriteSheetType> type { this, app::SpriteSheetType::None, "type" };
|
||||||
Param<gfx::Rect> frameBounds { this, gfx::Rect(0, 0, 0, 0), "frameBounds" };
|
Param<gfx::Rect> frameBounds { this, gfx::Rect(0, 0, 0, 0), "frameBounds" };
|
||||||
Param<gfx::Size> padding { this, gfx::Size(0, 0), "padding" };
|
Param<gfx::Size> padding { this, gfx::Size(0, 0), "padding" };
|
||||||
Param<bool> partialTiles { this, false, "partialTiles" };
|
Param<bool> partialTiles { this, false, "partialTiles" };
|
||||||
|
// Columns and rows are optional, and they just help in calculating
|
||||||
|
// frameBounds automatically when the number of columns and rows are known
|
||||||
|
// beforehand. So, if these are specified along frameBounds, then frameBounds
|
||||||
|
// width/height might be recalculated.
|
||||||
|
Param<int> columns { this, 0, "columns" };
|
||||||
|
Param<int> rows { this, 0, "rows" };
|
||||||
};
|
};
|
||||||
|
|
||||||
class ImportSpriteSheetWindow : public app::gen::ImportSpriteSheet
|
class ImportSpriteSheetWindow : public app::gen::ImportSpriteSheet
|
||||||
, public SelectBoxDelegate {
|
, public SelectBoxDelegate {
|
||||||
public:
|
public:
|
||||||
ImportSpriteSheetWindow(Context* context)
|
ImportSpriteSheetWindow(const ImportSpriteSheetParams& params,
|
||||||
|
Context* context)
|
||||||
: m_context(context)
|
: m_context(context)
|
||||||
, m_document(NULL)
|
, m_document(NULL)
|
||||||
, m_editor(NULL)
|
, m_editor(NULL)
|
||||||
@ -81,6 +104,8 @@ public:
|
|||||||
y()->Change.connect([this]{ onEntriesChange(); });
|
y()->Change.connect([this]{ onEntriesChange(); });
|
||||||
width()->Change.connect([this]{ onEntriesChange(); });
|
width()->Change.connect([this]{ onEntriesChange(); });
|
||||||
height()->Change.connect([this]{ onEntriesChange(); });
|
height()->Change.connect([this]{ onEntriesChange(); });
|
||||||
|
columns()->Change.connect([this]{ onColumnsChange(); });
|
||||||
|
rows()->Change.connect([this]{ onRowsChange(); });
|
||||||
paddingEnabled()->Click.connect([this]{ onPaddingEnabledChange(); });
|
paddingEnabled()->Click.connect([this]{ onPaddingEnabledChange(); });
|
||||||
horizontalPadding()->Change.connect([this]{ onEntriesChange(); });
|
horizontalPadding()->Change.connect([this]{ onEntriesChange(); });
|
||||||
verticalPadding()->Change.connect([this]{ onEntriesChange(); });
|
verticalPadding()->Change.connect([this]{ onEntriesChange(); });
|
||||||
@ -96,7 +121,40 @@ public:
|
|||||||
m_fileOpened = false;
|
m_fileOpened = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (params.type.isSet())
|
||||||
|
sheetType()->setSelectedItemIndex((int)params.type()-1);
|
||||||
|
|
||||||
|
if (params.frameBounds.isSet()) {
|
||||||
|
x()->setTextf("%d", params.frameBounds().x);
|
||||||
|
y()->setTextf("%d", params.frameBounds().y);
|
||||||
|
width()->setTextf("%d", params.frameBounds().w);
|
||||||
|
height()->setTextf("%d", params.frameBounds().h);
|
||||||
|
}
|
||||||
|
|
||||||
|
paddingEnabled()->setSelected(params.padding.isSet());
|
||||||
|
if (params.padding.isSet()) {
|
||||||
|
if (m_docPref)
|
||||||
|
m_docPref->importSpriteSheet.paddingBounds(params.padding());
|
||||||
|
else {
|
||||||
|
horizontalPadding()->setTextf("%d", params.padding().w);
|
||||||
|
verticalPadding()->setTextf("%d", params.padding().h);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (params.partialTiles.isSet())
|
||||||
|
partialTiles()->setSelected(params.partialTiles());
|
||||||
|
|
||||||
onPaddingEnabledChange();
|
onPaddingEnabledChange();
|
||||||
|
|
||||||
|
if (params.columns.isSet()) {
|
||||||
|
columns()->setTextf("%d", params.columns());
|
||||||
|
onColumnsChange();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (params.rows.isSet()) {
|
||||||
|
rows()->setTextf("%d", params.rows());
|
||||||
|
onRowsChange();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
~ImportSpriteSheetWindow() {
|
~ImportSpriteSheetWindow() {
|
||||||
@ -150,6 +208,7 @@ protected:
|
|||||||
|
|
||||||
void onSheetTypeChange() {
|
void onSheetTypeChange() {
|
||||||
updateGridState();
|
updateGridState();
|
||||||
|
onEntriesChange();
|
||||||
}
|
}
|
||||||
|
|
||||||
void onSelectFile() {
|
void onSelectFile() {
|
||||||
@ -187,7 +246,7 @@ protected:
|
|||||||
return gfx::Size(padW, padH);
|
return gfx::Size(padW, padH);
|
||||||
}
|
}
|
||||||
|
|
||||||
void onEntriesChange() {
|
void updateRulers() {
|
||||||
m_rect = getRectFromEntries();
|
m_rect = getRectFromEntries();
|
||||||
m_padding = getPaddingFromEntries();
|
m_padding = getPaddingFromEntries();
|
||||||
|
|
||||||
@ -205,6 +264,32 @@ protected:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void onEntriesChange() {
|
||||||
|
updateRulers();
|
||||||
|
columns()->setTextf("%d", calcColumns());
|
||||||
|
rows()->setTextf("%d", calcRows());
|
||||||
|
}
|
||||||
|
|
||||||
|
#define ON_COLROWS_CHANGE(widtheigth, wh) \
|
||||||
|
if (!m_editor) \
|
||||||
|
return; \
|
||||||
|
auto frameSize = calcFrameSize(m_editor->sprite()->size(), \
|
||||||
|
columns()->textInt(), rows()->textInt(), \
|
||||||
|
getRectFromEntries().origin(), \
|
||||||
|
getPaddingFromEntries()); \
|
||||||
|
widtheigth()->setTextf("%d", frameSize.wh); \
|
||||||
|
updateRulers();
|
||||||
|
|
||||||
|
void onColumnsChange() {
|
||||||
|
ON_COLROWS_CHANGE(width, w);
|
||||||
|
}
|
||||||
|
|
||||||
|
void onRowsChange() {
|
||||||
|
ON_COLROWS_CHANGE(height, h);
|
||||||
|
}
|
||||||
|
|
||||||
|
#undef ON_COLROWS_CHANGE
|
||||||
|
|
||||||
bool onProcessMessage(ui::Message* msg) override {
|
bool onProcessMessage(ui::Message* msg) override {
|
||||||
switch (msg->type()) {
|
switch (msg->type()) {
|
||||||
case kCloseMessage:
|
case kCloseMessage:
|
||||||
@ -223,6 +308,35 @@ protected:
|
|||||||
targets.push_back(View::getView(m_editor));
|
targets.push_back(View::getView(m_editor));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#define CALC_SPANS(xy, wh) \
|
||||||
|
if (!m_editor) \
|
||||||
|
return 0; \
|
||||||
|
int spans = 0; \
|
||||||
|
int rectwh = (m_rect.wh <= 0 ? 1 : m_rect.wh); \
|
||||||
|
int wh = m_editor->sprite()->size().wh - m_rect.xy; \
|
||||||
|
while(wh > 0) { \
|
||||||
|
wh -= rectwh; \
|
||||||
|
if (wh >= 0) { \
|
||||||
|
spans++; \
|
||||||
|
} \
|
||||||
|
wh -= m_padding.wh; \
|
||||||
|
} \
|
||||||
|
return spans;
|
||||||
|
|
||||||
|
int calcColumns() {
|
||||||
|
if (sheetTypeValue() == SpriteSheetType::Vertical)
|
||||||
|
return 1;
|
||||||
|
CALC_SPANS(x, w);
|
||||||
|
}
|
||||||
|
|
||||||
|
int calcRows() {
|
||||||
|
if (sheetTypeValue() == SpriteSheetType::Horizontal)
|
||||||
|
return 1;
|
||||||
|
CALC_SPANS(y, h);
|
||||||
|
}
|
||||||
|
|
||||||
|
#undef CALC_SPANS
|
||||||
|
|
||||||
// SelectBoxDelegate impleentation
|
// SelectBoxDelegate impleentation
|
||||||
void onChangeRectangle(const gfx::Rect& rect) override {
|
void onChangeRectangle(const gfx::Rect& rect) override {
|
||||||
m_rect = rect;
|
m_rect = rect;
|
||||||
@ -231,6 +345,8 @@ protected:
|
|||||||
y()->setTextf("%d", m_rect.y);
|
y()->setTextf("%d", m_rect.y);
|
||||||
width()->setTextf("%d", m_rect.w);
|
width()->setTextf("%d", m_rect.w);
|
||||||
height()->setTextf("%d", m_rect.h);
|
height()->setTextf("%d", m_rect.h);
|
||||||
|
columns()->setTextf("%d", calcColumns());
|
||||||
|
rows()->setTextf("%d", calcRows());
|
||||||
}
|
}
|
||||||
|
|
||||||
void onChangePadding(const gfx::Size& padding) override {
|
void onChangePadding(const gfx::Size& padding) override {
|
||||||
@ -248,6 +364,8 @@ protected:
|
|||||||
horizontalPadding()->setTextf("%d", 0);
|
horizontalPadding()->setTextf("%d", 0);
|
||||||
verticalPadding()->setTextf("%d", 0);
|
verticalPadding()->setTextf("%d", 0);
|
||||||
}
|
}
|
||||||
|
columns()->setTextf("%d", calcColumns());
|
||||||
|
rows()->setTextf("%d", calcRows());
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string onGetContextBarHelp() override {
|
std::string onGetContextBarHelp() override {
|
||||||
@ -340,13 +458,19 @@ private:
|
|||||||
switch (sheetTypeValue()) {
|
switch (sheetTypeValue()) {
|
||||||
case SpriteSheetType::Horizontal:
|
case SpriteSheetType::Horizontal:
|
||||||
flags |= int(SelectBoxState::Flags::HGrid);
|
flags |= int(SelectBoxState::Flags::HGrid);
|
||||||
|
columns()->setEnabled(true);
|
||||||
|
rows()->setEnabled(false);
|
||||||
break;
|
break;
|
||||||
case SpriteSheetType::Vertical:
|
case SpriteSheetType::Vertical:
|
||||||
flags |= int(SelectBoxState::Flags::VGrid);
|
flags |= int(SelectBoxState::Flags::VGrid);
|
||||||
|
columns()->setEnabled(false);
|
||||||
|
rows()->setEnabled(true);
|
||||||
break;
|
break;
|
||||||
case SpriteSheetType::Rows:
|
case SpriteSheetType::Rows:
|
||||||
case SpriteSheetType::Columns:
|
case SpriteSheetType::Columns:
|
||||||
flags |= int(SelectBoxState::Flags::Grid);
|
flags |= int(SelectBoxState::Flags::Grid);
|
||||||
|
columns()->setEnabled(true);
|
||||||
|
rows()->setEnabled(true);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -417,9 +541,7 @@ void ImportSpriteSheetCommand::onExecute(Context* context)
|
|||||||
auto& params = this->params();
|
auto& params = this->params();
|
||||||
|
|
||||||
if (context->isUIAvailable() && params.ui()) {
|
if (context->isUIAvailable() && params.ui()) {
|
||||||
// TODO use params as input values for the ImportSpriteSheetWindow
|
ImportSpriteSheetWindow window(params, context);
|
||||||
|
|
||||||
ImportSpriteSheetWindow window(context);
|
|
||||||
window.openWindowInForeground();
|
window.openWindowInForeground();
|
||||||
if (!window.ok())
|
if (!window.ok())
|
||||||
return;
|
return;
|
||||||
@ -443,6 +565,24 @@ void ImportSpriteSheetCommand::onExecute(Context* context)
|
|||||||
document = context->activeDocument();
|
document = context->activeDocument();
|
||||||
if (!document)
|
if (!document)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
Sprite* sprite = document->sprite();
|
||||||
|
|
||||||
|
auto newFrameBounds = calcFrameSize(sprite->size(),
|
||||||
|
params.columns(), params.rows(),
|
||||||
|
params.frameBounds().origin(),
|
||||||
|
params.padding());
|
||||||
|
if (params.columns.isSet()) {
|
||||||
|
auto fb = params.frameBounds();
|
||||||
|
fb.w = newFrameBounds.w;
|
||||||
|
params.frameBounds(fb);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (params.rows.isSet()) {
|
||||||
|
auto fb = params.frameBounds();
|
||||||
|
fb.h = newFrameBounds.h;
|
||||||
|
params.frameBounds(fb);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// The list of frames imported from the sheet
|
// The list of frames imported from the sheet
|
||||||
|
@ -14,6 +14,7 @@
|
|||||||
#include "app/color.h"
|
#include "app/color.h"
|
||||||
#include "app/color_utils.h"
|
#include "app/color_utils.h"
|
||||||
#include "app/commands/command.h"
|
#include "app/commands/command.h"
|
||||||
|
#include "app/commands/new_params.h"
|
||||||
#include "app/console.h"
|
#include "app/console.h"
|
||||||
#include "app/context.h"
|
#include "app/context.h"
|
||||||
#include "app/context_access.h"
|
#include "app/context_access.h"
|
||||||
@ -48,22 +49,96 @@ using namespace ui;
|
|||||||
|
|
||||||
static const char* ConfigSection = "MaskColor";
|
static const char* ConfigSection = "MaskColor";
|
||||||
|
|
||||||
class MaskByColorCommand : public Command {
|
struct MaskByColorParams : public NewParams {
|
||||||
public:
|
Param<bool> ui { this, true, "ui" };
|
||||||
MaskByColorCommand();
|
Param<app::Color> color { this, app::Color(), "color" };
|
||||||
|
Param<int> tolerance { this, 0, "tolerance" };
|
||||||
|
Param<gen::SelectionMode> mode { this, gen::SelectionMode::DEFAULT, "mode" };
|
||||||
|
};
|
||||||
|
|
||||||
protected:
|
class MaskByColorWindow : public ui::Window {
|
||||||
bool onEnabled(Context* context) override;
|
public:
|
||||||
void onExecute(Context* context) override;
|
MaskByColorWindow(MaskByColorParams& params, const ContextReader& reader)
|
||||||
|
: Window(Window::WithTitleBar, Strings::mask_by_color_title())
|
||||||
|
, m_reader(&reader)
|
||||||
|
// Save original mask visibility to process it correctly in
|
||||||
|
// ADD/SUBTRACT/INTERSECT Selection Mode
|
||||||
|
, m_isOrigMaskVisible(reader.document()->isMaskVisible()) {
|
||||||
|
|
||||||
|
TooltipManager* tooltipManager = new TooltipManager();
|
||||||
|
addChild(tooltipManager);
|
||||||
|
auto box1 = new Box(VERTICAL);
|
||||||
|
auto box2 = new Box(HORIZONTAL);
|
||||||
|
auto box3 = new Box(HORIZONTAL);
|
||||||
|
auto box4 = new Box(HORIZONTAL | HOMOGENEOUS);
|
||||||
|
auto label_color = new Label(Strings::mask_by_color_label_color());
|
||||||
|
m_buttonColor = new ColorButton(
|
||||||
|
params.color(),
|
||||||
|
reader.sprite()->pixelFormat(),
|
||||||
|
ColorButtonOptions());
|
||||||
|
auto label_tolerance = new Label(Strings::mask_by_color_tolerance());
|
||||||
|
m_sliderTolerance = new Slider(0, 255, params.tolerance());
|
||||||
|
|
||||||
|
m_selMode = new SelModeField;
|
||||||
|
m_selMode->setSelectionMode(params.mode());
|
||||||
|
m_selMode->setupTooltips(tooltipManager);
|
||||||
|
|
||||||
|
m_checkPreview = new CheckBox(Strings::mask_by_color_preview());
|
||||||
|
m_buttonOk = new Button(Strings::mask_by_color_ok());
|
||||||
|
auto button_cancel = new Button(Strings::mask_by_color_cancel());
|
||||||
|
|
||||||
|
m_checkPreview->processMnemonicFromText();
|
||||||
|
m_buttonOk->processMnemonicFromText();
|
||||||
|
button_cancel->processMnemonicFromText();
|
||||||
|
|
||||||
|
if (get_config_bool(ConfigSection, "Preview", true))
|
||||||
|
m_checkPreview->setSelected(true);
|
||||||
|
|
||||||
|
m_buttonOk->Click.connect([this]{ closeWindow(m_buttonOk); });
|
||||||
|
button_cancel->Click.connect([this, button_cancel]{ closeWindow(button_cancel); });
|
||||||
|
|
||||||
|
m_buttonColor->Change.connect([&]{ maskPreview(); });
|
||||||
|
m_sliderTolerance->Change.connect([&]{ maskPreview(); });
|
||||||
|
m_checkPreview->Click.connect([&]{ maskPreview(); });
|
||||||
|
m_selMode->ModeChange.connect([&]{ maskPreview(); });
|
||||||
|
|
||||||
|
m_buttonOk->setFocusMagnet(true);
|
||||||
|
m_buttonColor->setExpansive(true);
|
||||||
|
m_sliderTolerance->setExpansive(true);
|
||||||
|
box2->setExpansive(true);
|
||||||
|
|
||||||
|
addChild(box1);
|
||||||
|
box1->addChild(m_selMode);
|
||||||
|
box1->addChild(box2);
|
||||||
|
box1->addChild(box3);
|
||||||
|
box1->addChild(m_checkPreview);
|
||||||
|
box1->addChild(box4);
|
||||||
|
box2->addChild(label_color);
|
||||||
|
box2->addChild(m_buttonColor);
|
||||||
|
box3->addChild(label_tolerance);
|
||||||
|
box3->addChild(m_sliderTolerance);
|
||||||
|
box4->addChild(m_buttonOk);
|
||||||
|
box4->addChild(button_cancel);
|
||||||
|
|
||||||
|
// Default position
|
||||||
|
remapWindow();
|
||||||
|
centerWindow();
|
||||||
|
|
||||||
|
// Mask first preview
|
||||||
|
maskPreview();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool accepted() const { return closer() == m_buttonOk; }
|
||||||
|
|
||||||
|
app::Color getColor() const { return m_buttonColor->getColor(); }
|
||||||
|
|
||||||
|
int getTolerance() const { return m_sliderTolerance->getValue(); }
|
||||||
|
|
||||||
|
gen::SelectionMode getSelectionMode() const { return m_selMode->selectionMode(); }
|
||||||
|
|
||||||
|
bool isPreviewChecked() const { return m_checkPreview->isSelected(); }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
Mask* generateMask(const Mask& origMask,
|
|
||||||
const Sprite* sprite,
|
|
||||||
const Image* image,
|
|
||||||
int xpos, int ypos,
|
|
||||||
gen::SelectionMode mode);
|
|
||||||
void maskPreview(const ContextReader& reader);
|
|
||||||
|
|
||||||
class SelModeField : public SelectionModeField {
|
class SelModeField : public SelectionModeField {
|
||||||
public:
|
public:
|
||||||
obs::signal<void()> ModeChange;
|
obs::signal<void()> ModeChange;
|
||||||
@ -73,157 +148,31 @@ private:
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
Window* m_window = nullptr;
|
void maskPreview();
|
||||||
|
|
||||||
|
const ContextReader* m_reader = nullptr;
|
||||||
|
bool m_isOrigMaskVisible;
|
||||||
|
Button* m_buttonOk = nullptr;
|
||||||
ColorButton* m_buttonColor = nullptr;
|
ColorButton* m_buttonColor = nullptr;
|
||||||
CheckBox* m_checkPreview = nullptr;
|
CheckBox* m_checkPreview = nullptr;
|
||||||
Slider* m_sliderTolerance = nullptr;
|
Slider* m_sliderTolerance = nullptr;
|
||||||
SelModeField* m_selMode = nullptr;
|
SelModeField* m_selMode = nullptr;
|
||||||
bool m_isOrigMaskVisible;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
MaskByColorCommand::MaskByColorCommand()
|
static Mask* generateMask(const Mask& origMask,
|
||||||
: Command(CommandId::MaskByColor(), CmdUIOnlyFlag)
|
bool isOrigMaskVisible,
|
||||||
|
const Image* image,
|
||||||
|
int xpos, int ypos,
|
||||||
|
gen::SelectionMode mode,
|
||||||
|
int color,
|
||||||
|
int tolerance)
|
||||||
{
|
{
|
||||||
}
|
|
||||||
|
|
||||||
bool MaskByColorCommand::onEnabled(Context* context)
|
|
||||||
{
|
|
||||||
return context->checkFlags(ContextFlags::ActiveDocumentIsWritable |
|
|
||||||
ContextFlags::HasActiveSprite |
|
|
||||||
ContextFlags::HasActiveImage);
|
|
||||||
}
|
|
||||||
|
|
||||||
void MaskByColorCommand::onExecute(Context* context)
|
|
||||||
{
|
|
||||||
ASSERT(!m_window);
|
|
||||||
|
|
||||||
const ContextReader reader(context);
|
|
||||||
const Sprite* sprite = reader.sprite();
|
|
||||||
|
|
||||||
if (!App::instance()->isGui() || !sprite)
|
|
||||||
return;
|
|
||||||
|
|
||||||
int xpos, ypos;
|
|
||||||
const Image* image = reader.image(&xpos, &ypos);
|
|
||||||
if (!image)
|
|
||||||
return;
|
|
||||||
|
|
||||||
std::unique_ptr<Window> win(
|
|
||||||
new Window(Window::WithTitleBar, Strings::mask_by_color_title()));
|
|
||||||
base::ScopedValue<Window*> setWindow(m_window, win.get(), nullptr);
|
|
||||||
TooltipManager* tooltipManager = new TooltipManager();
|
|
||||||
m_window->addChild(tooltipManager);
|
|
||||||
auto box1 = new Box(VERTICAL);
|
|
||||||
auto box2 = new Box(HORIZONTAL);
|
|
||||||
auto box3 = new Box(HORIZONTAL);
|
|
||||||
auto box4 = new Box(HORIZONTAL | HOMOGENEOUS);
|
|
||||||
auto label_color = new Label(Strings::mask_by_color_label_color());
|
|
||||||
m_buttonColor = new ColorButton(
|
|
||||||
ColorBar::instance()->getFgColor(),
|
|
||||||
sprite->pixelFormat(),
|
|
||||||
ColorButtonOptions());
|
|
||||||
auto label_tolerance = new Label(Strings::mask_by_color_tolerance());
|
|
||||||
m_sliderTolerance = new Slider(0, 255, get_config_int(ConfigSection, "Tolerance", 0));
|
|
||||||
|
|
||||||
m_selMode = new SelModeField;
|
|
||||||
m_selMode->setupTooltips(tooltipManager);
|
|
||||||
|
|
||||||
m_checkPreview = new CheckBox(Strings::mask_by_color_preview());
|
|
||||||
auto button_ok = new Button(Strings::mask_by_color_ok());
|
|
||||||
auto button_cancel = new Button(Strings::mask_by_color_cancel());
|
|
||||||
|
|
||||||
m_checkPreview->processMnemonicFromText();
|
|
||||||
button_ok->processMnemonicFromText();
|
|
||||||
button_cancel->processMnemonicFromText();
|
|
||||||
|
|
||||||
if (get_config_bool(ConfigSection, "Preview", true))
|
|
||||||
m_checkPreview->setSelected(true);
|
|
||||||
|
|
||||||
button_ok->Click.connect([this, button_ok]{ m_window->closeWindow(button_ok); });
|
|
||||||
button_cancel->Click.connect([this, button_cancel]{ m_window->closeWindow(button_cancel); });
|
|
||||||
|
|
||||||
m_buttonColor->Change.connect([&]{ maskPreview(reader); });
|
|
||||||
m_sliderTolerance->Change.connect([&]{ maskPreview(reader); });
|
|
||||||
m_checkPreview->Click.connect([&]{ maskPreview(reader); });
|
|
||||||
m_selMode->ModeChange.connect([&]{ maskPreview(reader); });
|
|
||||||
|
|
||||||
button_ok->setFocusMagnet(true);
|
|
||||||
m_buttonColor->setExpansive(true);
|
|
||||||
m_sliderTolerance->setExpansive(true);
|
|
||||||
box2->setExpansive(true);
|
|
||||||
|
|
||||||
m_window->addChild(box1);
|
|
||||||
box1->addChild(m_selMode);
|
|
||||||
box1->addChild(box2);
|
|
||||||
box1->addChild(box3);
|
|
||||||
box1->addChild(m_checkPreview);
|
|
||||||
box1->addChild(box4);
|
|
||||||
box2->addChild(label_color);
|
|
||||||
box2->addChild(m_buttonColor);
|
|
||||||
box3->addChild(label_tolerance);
|
|
||||||
box3->addChild(m_sliderTolerance);
|
|
||||||
box4->addChild(button_ok);
|
|
||||||
box4->addChild(button_cancel);
|
|
||||||
|
|
||||||
// Default position
|
|
||||||
m_window->remapWindow();
|
|
||||||
m_window->centerWindow();
|
|
||||||
|
|
||||||
// Save original mask visibility to process it correctly in
|
|
||||||
// ADD/SUBTRACT/INTERSECT Selection Mode
|
|
||||||
m_isOrigMaskVisible = reader.document()->isMaskVisible();
|
|
||||||
|
|
||||||
// Mask first preview
|
|
||||||
maskPreview(reader);
|
|
||||||
|
|
||||||
// Load window configuration
|
|
||||||
load_window_pos(m_window, ConfigSection);
|
|
||||||
|
|
||||||
// Open the window
|
|
||||||
m_window->openWindowInForeground();
|
|
||||||
|
|
||||||
bool apply = (m_window->closer() == button_ok);
|
|
||||||
|
|
||||||
ContextWriter writer(reader);
|
|
||||||
Doc* document(writer.document());
|
|
||||||
|
|
||||||
if (apply) {
|
|
||||||
Tx tx(writer, "Mask by Color", DoesntModifyDocument);
|
|
||||||
std::unique_ptr<Mask> mask(generateMask(*document->mask(),
|
|
||||||
sprite, image, xpos, ypos,
|
|
||||||
m_selMode->selectionMode()));
|
|
||||||
tx(new cmd::SetMask(document, mask.get()));
|
|
||||||
tx.commit();
|
|
||||||
|
|
||||||
set_config_int(ConfigSection, "Tolerance", m_sliderTolerance->getValue());
|
|
||||||
set_config_bool(ConfigSection, "Preview", m_checkPreview->isSelected());
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
document->generateMaskBoundaries();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update boundaries and editors.
|
|
||||||
update_screen_for_document(document);
|
|
||||||
|
|
||||||
// Save window configuration.
|
|
||||||
save_window_pos(m_window, ConfigSection);
|
|
||||||
}
|
|
||||||
|
|
||||||
Mask* MaskByColorCommand::generateMask(const Mask& origMask,
|
|
||||||
const Sprite* sprite,
|
|
||||||
const Image* image,
|
|
||||||
int xpos, int ypos,
|
|
||||||
gen::SelectionMode mode)
|
|
||||||
{
|
|
||||||
int color = color_utils::color_for_image(m_buttonColor->getColor(),
|
|
||||||
sprite->pixelFormat());
|
|
||||||
int tolerance = m_sliderTolerance->getValue();
|
|
||||||
|
|
||||||
std::unique_ptr<Mask> mask(new Mask());
|
std::unique_ptr<Mask> mask(new Mask());
|
||||||
mask->byColor(image, color, tolerance);
|
mask->byColor(image, color, tolerance);
|
||||||
mask->offsetOrigin(xpos, ypos);
|
mask->offsetOrigin(xpos, ypos);
|
||||||
|
|
||||||
if (!origMask.isEmpty() && m_isOrigMaskVisible) {
|
if (!origMask.isEmpty() && isOrigMaskVisible) {
|
||||||
switch (mode) {
|
switch (mode) {
|
||||||
case gen::SelectionMode::DEFAULT:
|
case gen::SelectionMode::DEFAULT:
|
||||||
break;
|
break;
|
||||||
@ -251,18 +200,123 @@ Mask* MaskByColorCommand::generateMask(const Mask& origMask,
|
|||||||
return mask.release();
|
return mask.release();
|
||||||
}
|
}
|
||||||
|
|
||||||
void MaskByColorCommand::maskPreview(const ContextReader& reader)
|
class MaskByColorCommand : public CommandWithNewParams<MaskByColorParams> {
|
||||||
{
|
public:
|
||||||
ASSERT(m_window);
|
MaskByColorCommand();
|
||||||
if (m_window && m_checkPreview->isSelected()) {
|
|
||||||
int xpos, ypos;
|
|
||||||
const Image* image = reader.image(&xpos, &ypos);
|
|
||||||
std::unique_ptr<Mask> mask(generateMask(*reader.document()->mask(),
|
|
||||||
reader.sprite(), image,
|
|
||||||
xpos, ypos,
|
|
||||||
m_selMode->selectionMode()));
|
|
||||||
|
|
||||||
ContextWriter writer(reader);
|
protected:
|
||||||
|
bool onEnabled(Context* context) override;
|
||||||
|
void onExecute(Context* context) override;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
MaskByColorCommand::MaskByColorCommand()
|
||||||
|
: CommandWithNewParams(CommandId::MaskByColor(), CmdUIOnlyFlag)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
bool MaskByColorCommand::onEnabled(Context* context)
|
||||||
|
{
|
||||||
|
return context->checkFlags(ContextFlags::ActiveDocumentIsWritable |
|
||||||
|
ContextFlags::HasActiveSprite |
|
||||||
|
ContextFlags::HasActiveImage);
|
||||||
|
}
|
||||||
|
|
||||||
|
void MaskByColorCommand::onExecute(Context* context)
|
||||||
|
{
|
||||||
|
const bool ui = (params().ui() && context->isUIAvailable());
|
||||||
|
const ContextReader reader(context);
|
||||||
|
const Sprite* sprite = reader.sprite();
|
||||||
|
|
||||||
|
if (!sprite)
|
||||||
|
return;
|
||||||
|
|
||||||
|
int xpos, ypos;
|
||||||
|
const Image* image = reader.image(&xpos, &ypos);
|
||||||
|
if (!image)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// Save original mask visibility to process it correctly in
|
||||||
|
// ADD/SUBTRACT/INTERSECT Selection Mode
|
||||||
|
bool isOrigMaskVisible = reader.document()->isMaskVisible();
|
||||||
|
|
||||||
|
bool apply = true;
|
||||||
|
auto& params = this->params();
|
||||||
|
|
||||||
|
// If UI is available, set parameters default values from the UI/configuration
|
||||||
|
if (context->isUIAvailable()) {
|
||||||
|
if (!params.color.isSet())
|
||||||
|
params.color(ColorBar::instance()->getFgColor());
|
||||||
|
|
||||||
|
if (!params.tolerance.isSet())
|
||||||
|
params.tolerance(get_config_int(ConfigSection, "Tolerance", 0));
|
||||||
|
|
||||||
|
if (!params.mode.isSet())
|
||||||
|
params.mode(Preferences::instance().selection.mode());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ui) {
|
||||||
|
MaskByColorWindow window(params, reader);
|
||||||
|
|
||||||
|
// Load window configuration
|
||||||
|
load_window_pos(&window, ConfigSection);
|
||||||
|
|
||||||
|
// Open the window
|
||||||
|
window.openWindowInForeground();
|
||||||
|
|
||||||
|
// Save window configuration.
|
||||||
|
save_window_pos(&window, ConfigSection);
|
||||||
|
|
||||||
|
apply = window.accepted();
|
||||||
|
if (apply) {
|
||||||
|
params.color(window.getColor());
|
||||||
|
params.mode(window.getSelectionMode());
|
||||||
|
params.tolerance(window.getTolerance());
|
||||||
|
|
||||||
|
set_config_int(ConfigSection, "Tolerance", params.tolerance());
|
||||||
|
set_config_bool(ConfigSection, "Preview", window.isPreviewChecked());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ContextWriter writer(reader);
|
||||||
|
Doc* document(writer.document());
|
||||||
|
|
||||||
|
if (apply) {
|
||||||
|
int color = color_utils::color_for_image(params.color(),
|
||||||
|
sprite->pixelFormat());
|
||||||
|
|
||||||
|
Tx tx(writer, "Mask by Color", DoesntModifyDocument);
|
||||||
|
std::unique_ptr<Mask> mask(generateMask(*document->mask(),
|
||||||
|
isOrigMaskVisible,
|
||||||
|
image, xpos, ypos,
|
||||||
|
params.mode(),
|
||||||
|
color, params.tolerance()));
|
||||||
|
tx(new cmd::SetMask(document, mask.get()));
|
||||||
|
tx.commit();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
document->generateMaskBoundaries();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update boundaries and editors.
|
||||||
|
update_screen_for_document(document);
|
||||||
|
}
|
||||||
|
|
||||||
|
void MaskByColorWindow::maskPreview()
|
||||||
|
{
|
||||||
|
if (isPreviewChecked()) {
|
||||||
|
int xpos, ypos;
|
||||||
|
const Image* image = m_reader->image(&xpos, &ypos);
|
||||||
|
int color = color_utils::color_for_image(m_buttonColor->getColor(),
|
||||||
|
m_reader->sprite()->pixelFormat());
|
||||||
|
int tolerance = m_sliderTolerance->getValue();
|
||||||
|
std::unique_ptr<Mask> mask(generateMask(*m_reader->document()->mask(),
|
||||||
|
m_isOrigMaskVisible,
|
||||||
|
image, xpos, ypos,
|
||||||
|
m_selMode->selectionMode(),
|
||||||
|
color, tolerance));
|
||||||
|
|
||||||
|
ContextWriter writer(*m_reader);
|
||||||
|
|
||||||
#ifdef SHOW_BOUNDARIES_GEN_PERFORMANCE
|
#ifdef SHOW_BOUNDARIES_GEN_PERFORMANCE
|
||||||
base::Chrono chrono;
|
base::Chrono chrono;
|
||||||
|
@ -12,6 +12,7 @@
|
|||||||
|
|
||||||
#include "app/color.h"
|
#include "app/color.h"
|
||||||
#include "app/doc_exporter.h"
|
#include "app/doc_exporter.h"
|
||||||
|
#include "app/pref/preferences.h"
|
||||||
#include "app/sprite_sheet_type.h"
|
#include "app/sprite_sheet_type.h"
|
||||||
#include "app/tools/ink_type.h"
|
#include "app/tools/ink_type.h"
|
||||||
#include "base/convert_to.h"
|
#include "base/convert_to.h"
|
||||||
@ -241,6 +242,21 @@ void Param<doc::RgbMapAlgorithm>::fromString(const std::string& value)
|
|||||||
setValue(doc::RgbMapAlgorithm::DEFAULT);
|
setValue(doc::RgbMapAlgorithm::DEFAULT);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
template<>
|
||||||
|
void Param<gen::SelectionMode>::fromString(const std::string& value)
|
||||||
|
{
|
||||||
|
if (base::utf8_icmp(value, "replace") == 0)
|
||||||
|
setValue(gen::SelectionMode::REPLACE);
|
||||||
|
else if (base::utf8_icmp(value, "add") == 0)
|
||||||
|
setValue(gen::SelectionMode::ADD);
|
||||||
|
else if (base::utf8_icmp(value, "subtract") == 0)
|
||||||
|
setValue(gen::SelectionMode::SUBTRACT);
|
||||||
|
else if (base::utf8_icmp(value, "intersect") == 0)
|
||||||
|
setValue(gen::SelectionMode::INTERSECT);
|
||||||
|
else
|
||||||
|
setValue(gen::SelectionMode::DEFAULT);
|
||||||
|
}
|
||||||
|
|
||||||
//////////////////////////////////////////////////////////////////////
|
//////////////////////////////////////////////////////////////////////
|
||||||
// Convert values from Lua
|
// Convert values from Lua
|
||||||
//////////////////////////////////////////////////////////////////////
|
//////////////////////////////////////////////////////////////////////
|
||||||
@ -405,6 +421,15 @@ void Param<doc::RgbMapAlgorithm>::fromLua(lua_State* L, int index)
|
|||||||
setValue((doc::RgbMapAlgorithm)lua_tointeger(L, index));
|
setValue((doc::RgbMapAlgorithm)lua_tointeger(L, index));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
template<>
|
||||||
|
void Param<gen::SelectionMode>::fromLua(lua_State* L, int index)
|
||||||
|
{
|
||||||
|
if (lua_type(L, index) == LUA_TSTRING)
|
||||||
|
fromString(lua_tostring(L, index));
|
||||||
|
else
|
||||||
|
setValue((gen::SelectionMode)lua_tointeger(L, index));
|
||||||
|
}
|
||||||
|
|
||||||
void CommandWithNewParamsBase::loadParamsFromLuaTable(lua_State* L, int index)
|
void CommandWithNewParamsBase::loadParamsFromLuaTable(lua_State* L, int index)
|
||||||
{
|
{
|
||||||
onResetValues();
|
onResetValues();
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
// Aseprite
|
// Aseprite
|
||||||
// Copyright (C) 2018-2023 Igara Studio S.A.
|
// Copyright (C) 2018-2024 Igara Studio S.A.
|
||||||
// Copyright (C) 2018 David Capello
|
// Copyright (C) 2018 David Capello
|
||||||
//
|
//
|
||||||
// This program is distributed under the terms of
|
// This program is distributed under the terms of
|
||||||
@ -9,6 +9,7 @@
|
|||||||
#include "config.h"
|
#include "config.h"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#include "app/cmd/remove_cel.h"
|
||||||
#include "app/cmd/replace_image.h"
|
#include "app/cmd/replace_image.h"
|
||||||
#include "app/cmd/set_cel_opacity.h"
|
#include "app/cmd/set_cel_opacity.h"
|
||||||
#include "app/cmd/set_cel_position.h"
|
#include "app/cmd/set_cel_position.h"
|
||||||
@ -122,13 +123,16 @@ int Cel_set_frame(lua_State* L)
|
|||||||
int Cel_set_image(lua_State* L)
|
int Cel_set_image(lua_State* L)
|
||||||
{
|
{
|
||||||
auto cel = get_docobj<Cel>(L, 1);
|
auto cel = get_docobj<Cel>(L, 1);
|
||||||
auto srcImage = get_image_from_arg(L, 2);
|
|
||||||
ImageRef newImage(Image::createCopy(srcImage));
|
|
||||||
|
|
||||||
Tx tx(cel->sprite());
|
Tx tx(cel->sprite());
|
||||||
tx(new cmd::ReplaceImage(cel->sprite(),
|
if (may_get_obj<Image>(L, 2)) {
|
||||||
cel->imageRef(),
|
const auto* srcImage = get_image_from_arg(L, 2);
|
||||||
newImage));
|
const ImageRef newImage(Image::createCopy(srcImage));
|
||||||
|
tx(new cmd::ReplaceImage(cel->sprite(),
|
||||||
|
cel->imageRef(),
|
||||||
|
newImage));
|
||||||
|
}
|
||||||
|
else if (lua_isnil(L, 2))
|
||||||
|
tx(new cmd::RemoveCel(cel));
|
||||||
tx.commit();
|
tx.commit();
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
// Aseprite
|
// Aseprite
|
||||||
// Copyright (C) 2019-2022 Igara Studio S.A.
|
// Copyright (C) 2019-2024 Igara Studio S.A.
|
||||||
// Copyright (C) 2001-2017 David Capello
|
// Copyright (C) 2001-2017 David Capello
|
||||||
//
|
//
|
||||||
// This program is distributed under the terms of
|
// This program is distributed under the terms of
|
||||||
@ -207,6 +207,7 @@ namespace app {
|
|||||||
virtual const doc::Grid& getGrid() const = 0;
|
virtual const doc::Grid& getGrid() const = 0;
|
||||||
virtual gfx::Rect getGridBounds() = 0;
|
virtual gfx::Rect getGridBounds() = 0;
|
||||||
virtual bool isPixelConnectivityEightConnected() = 0;
|
virtual bool isPixelConnectivityEightConnected() = 0;
|
||||||
|
virtual bool isPointInsideCanvas(const gfx::Point& point) = 0;
|
||||||
|
|
||||||
// Returns true if the figure must be filled when we release the
|
// Returns true if the figure must be filled when we release the
|
||||||
// mouse (e.g. a filled rectangle, etc.)
|
// mouse (e.g. a filled rectangle, etc.)
|
||||||
|
@ -153,9 +153,8 @@ bool ToolLoopManager::releaseButton(const Pointer& pointer)
|
|||||||
|
|
||||||
if (m_toolLoop->getController()->isOnePoint() &&
|
if (m_toolLoop->getController()->isOnePoint() &&
|
||||||
m_toolLoop->getInk()->isSelection() &&
|
m_toolLoop->getInk()->isSelection() &&
|
||||||
!m_toolLoop->getSrcImage()->bounds().contains(pointer.point())) {
|
!m_toolLoop->isPointInsideCanvas(pointer.point()))
|
||||||
return false;
|
return false;
|
||||||
}
|
|
||||||
|
|
||||||
Stroke::Pt spritePoint = getSpriteStrokePt(pointer);
|
Stroke::Pt spritePoint = getSpriteStrokePt(pointer);
|
||||||
bool res = m_toolLoop->getController()->releaseButton(m_stroke, spritePoint);
|
bool res = m_toolLoop->getController()->releaseButton(m_stroke, spritePoint);
|
||||||
|
@ -609,6 +609,12 @@ bool StandbyState::onUpdateStatusBar(Editor* editor)
|
|||||||
gfx::Point pt = grid.canvasToTile(gfx::Point(spritePos));
|
gfx::Point pt = grid.canvasToTile(gfx::Point(spritePos));
|
||||||
buf += fmt::format(" :grid: {} {}", pt.x, pt.y);
|
buf += fmt::format(" :grid: {} {}", pt.x, pt.y);
|
||||||
|
|
||||||
|
// Number of columns of the current grid/tilemap to show a
|
||||||
|
// "tile index", i.e. a linear index that might be used in a
|
||||||
|
// 1D array to represent the current grid tile/cell. If it's 0
|
||||||
|
// we don't show the index.
|
||||||
|
int tileIndexColumns = 0;
|
||||||
|
|
||||||
// Show the tile index of this specific tile
|
// Show the tile index of this specific tile
|
||||||
if (site.layer() &&
|
if (site.layer() &&
|
||||||
site.layer()->isTilemap() &&
|
site.layer()->isTilemap() &&
|
||||||
@ -620,8 +626,20 @@ bool StandbyState::onUpdateStatusBar(Editor* editor)
|
|||||||
std::string str;
|
std::string str;
|
||||||
build_tile_flags_string(tf, str);
|
build_tile_flags_string(tf, str);
|
||||||
buf += fmt::format(" [{}{}]", ti, str);
|
buf += fmt::format(" [{}{}]", ti, str);
|
||||||
|
|
||||||
|
// Show tile index for a tilemaps (using the tilemap size)
|
||||||
|
tileIndexColumns = site.image()->width();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// Show the tile index for a regular layer/grid
|
||||||
|
else if (sprite->bounds().contains(gfx::Point(spritePos))) {
|
||||||
|
tileIndexColumns =
|
||||||
|
int(std::ceil(double(sprite->bounds().w - grid.origin().x)
|
||||||
|
/ grid.tileSize().w));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tileIndexColumns > 0 && pt.x >= 0 && pt.y >= 0)
|
||||||
|
buf += fmt::format(" :search: {}", pt.x+pt.y*tileIndexColumns);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -360,6 +360,15 @@ public:
|
|||||||
== app::gen::PixelConnectivity::EIGHT_CONNECTED);
|
== app::gen::PixelConnectivity::EIGHT_CONNECTED);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool isPointInsideCanvas(const gfx::Point& point) override {
|
||||||
|
const int a = ((getTiledMode() == TiledMode::X_AXIS ||
|
||||||
|
getTiledMode() == TiledMode::BOTH) ? 3 : 1);
|
||||||
|
const int b = ((getTiledMode() == TiledMode::Y_AXIS ||
|
||||||
|
getTiledMode() == TiledMode::BOTH) ? 3 : 1);
|
||||||
|
return 0 <= point.x && point.x < a * sprite()->size().w &&
|
||||||
|
0 <= point.y && point.y < b * sprite()->size().h;
|
||||||
|
}
|
||||||
|
|
||||||
const doc::Grid& getGrid() const override { return m_grid; }
|
const doc::Grid& getGrid() const override { return m_grid; }
|
||||||
gfx::Rect getGridBounds() override { return m_gridBounds; }
|
gfx::Rect getGridBounds() override { return m_gridBounds; }
|
||||||
gfx::Point getCelOrigin() override { return m_celOrigin; }
|
gfx::Point getCelOrigin() override { return m_celOrigin; }
|
||||||
|
@ -86,11 +86,7 @@ bool ButtonBase::onProcessMessage(Message* msg)
|
|||||||
|
|
||||||
if (isEnabled() && isVisible()) {
|
if (isEnabled() && isVisible()) {
|
||||||
const bool mnemonicPressed =
|
const bool mnemonicPressed =
|
||||||
(mnemonic() &&
|
isMnemonicPressedWithModifiers(keymsg);
|
||||||
(!mnemonicRequiresModifiers() ||
|
|
||||||
msg->altPressed() ||
|
|
||||||
msg->cmdPressed()) &&
|
|
||||||
isMnemonicPressed(keymsg));
|
|
||||||
|
|
||||||
// For kButtonWidget
|
// For kButtonWidget
|
||||||
if (m_behaviorType == kButtonWidget) {
|
if (m_behaviorType == kButtonWidget) {
|
||||||
@ -154,11 +150,19 @@ bool ButtonBase::onProcessMessage(Message* msg)
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case kCheckWidget: {
|
case kCheckWidget: {
|
||||||
// Fire onClick() event
|
KeyMessage* keymsg = static_cast<KeyMessage*>(msg);
|
||||||
onClick();
|
KeyScancode scancode = keymsg->scancode();
|
||||||
return true;
|
const bool mnemonicPressed =
|
||||||
}
|
isMnemonicPressedWithModifiers(keymsg);
|
||||||
|
|
||||||
|
// Fire the onClick() event only if the user pressed space or
|
||||||
|
// Alt+the underscored letter of the checkbox label.
|
||||||
|
if (scancode == kKeySpace || mnemonicPressed) {
|
||||||
|
onClick();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
@ -1586,6 +1586,15 @@ bool Widget::isMnemonicPressed(const KeyMessage* keyMsg) const
|
|||||||
(chr >= '0' && chr <= '9' && keyMsg->scancode() == (kKey0 + chr - '0'))));
|
(chr >= '0' && chr <= '9' && keyMsg->scancode() == (kKey0 + chr - '0'))));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool Widget::isMnemonicPressedWithModifiers(const KeyMessage* msg) const
|
||||||
|
{
|
||||||
|
return (mnemonic() &&
|
||||||
|
(!mnemonicRequiresModifiers() ||
|
||||||
|
msg->altPressed() ||
|
||||||
|
msg->cmdPressed()) &&
|
||||||
|
isMnemonicPressed(msg));
|
||||||
|
}
|
||||||
|
|
||||||
bool Widget::onProcessMessage(Message* msg)
|
bool Widget::onProcessMessage(Message* msg)
|
||||||
{
|
{
|
||||||
ASSERT(msg != nullptr);
|
ASSERT(msg != nullptr);
|
||||||
|
@ -388,7 +388,7 @@ namespace ui {
|
|||||||
|
|
||||||
// Offer the capture to widgets of the given type. Returns true if
|
// Offer the capture to widgets of the given type. Returns true if
|
||||||
// the capture was passed to other widget.
|
// the capture was passed to other widget.
|
||||||
bool offerCapture(ui::MouseMessage* mouseMsg, int widget_type);
|
bool offerCapture(MouseMessage* mouseMsg, int widget_type);
|
||||||
|
|
||||||
// Returns lower-case letter that represet the mnemonic of the widget
|
// Returns lower-case letter that represet the mnemonic of the widget
|
||||||
// (the underscored character, i.e. the letter after & symbol).
|
// (the underscored character, i.e. the letter after & symbol).
|
||||||
@ -409,7 +409,11 @@ namespace ui {
|
|||||||
// Returns true if the mnemonic character is pressed (without modifiers).
|
// Returns true if the mnemonic character is pressed (without modifiers).
|
||||||
// TODO maybe we can add check for modifiers now that this
|
// TODO maybe we can add check for modifiers now that this
|
||||||
// information is included in the Widget
|
// information is included in the Widget
|
||||||
bool isMnemonicPressed(const ui::KeyMessage* keyMsg) const;
|
bool isMnemonicPressed(const KeyMessage* keyMsg) const;
|
||||||
|
|
||||||
|
// Returns true if the mnemonic character is pressed with
|
||||||
|
// modifiers (Alt or Command key).
|
||||||
|
bool isMnemonicPressedWithModifiers(const KeyMessage* msg) const;
|
||||||
|
|
||||||
// Signals
|
// Signals
|
||||||
obs::signal<void()> InitTheme;
|
obs::signal<void()> InitTheme;
|
||||||
|
@ -664,3 +664,102 @@ do
|
|||||||
expect_img(i, { 1, 1, 1, 1,
|
expect_img(i, { 1, 1, 1, 1,
|
||||||
1, 0, 0, 1 })
|
1, 0, 0, 1 })
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- MaskByColor
|
||||||
|
do
|
||||||
|
local s = Sprite(5, 5, ColorMode.INDEXED)
|
||||||
|
local c = s.cels[1]
|
||||||
|
local i = c.image
|
||||||
|
array_to_pixels({
|
||||||
|
1, 1, 0, 0, 1,
|
||||||
|
1, 1, 0, 0, 1,
|
||||||
|
1, 0, 0, 0, 1,
|
||||||
|
1, 0, 0, 1, 1,
|
||||||
|
1, 0, 0, 1, 1,
|
||||||
|
}, i)
|
||||||
|
|
||||||
|
app.command.MaskByColor {
|
||||||
|
color = Color{ index=1 },
|
||||||
|
tolerance = 0,
|
||||||
|
}
|
||||||
|
app.fgColor = Color{ index=2 }
|
||||||
|
app.command.Fill()
|
||||||
|
|
||||||
|
expect_img(i, {
|
||||||
|
2, 2, 0, 0, 2,
|
||||||
|
2, 2, 0, 0, 2,
|
||||||
|
2, 0, 0, 0, 2,
|
||||||
|
2, 0, 0, 2, 2,
|
||||||
|
2, 0, 0, 2, 2,
|
||||||
|
})
|
||||||
|
|
||||||
|
-- Subtract from current selection by color
|
||||||
|
app.command.MaskAll {}
|
||||||
|
app.command.MaskByColor {
|
||||||
|
color = Color{ index=2 },
|
||||||
|
tolerance = 0,
|
||||||
|
mode = SelectionMode.SUBTRACT,
|
||||||
|
}
|
||||||
|
|
||||||
|
app.fgColor = Color{ index=3 }
|
||||||
|
app.command.Fill()
|
||||||
|
|
||||||
|
expect_img(i, {
|
||||||
|
2, 2, 3, 3, 2,
|
||||||
|
2, 2, 3, 3, 2,
|
||||||
|
2, 3, 3, 3, 2,
|
||||||
|
2, 3, 3, 2, 2,
|
||||||
|
2, 3, 3, 2, 2,
|
||||||
|
})
|
||||||
|
|
||||||
|
-- Add to current selection by color
|
||||||
|
app.command.MaskByColor {
|
||||||
|
color = Color{ index=2 },
|
||||||
|
tolerance = 0,
|
||||||
|
mode = SelectionMode.ADD,
|
||||||
|
}
|
||||||
|
app.fgColor = Color{ index=4 }
|
||||||
|
app.command.Fill()
|
||||||
|
|
||||||
|
expect_img(i, {
|
||||||
|
4, 4, 4, 4, 4,
|
||||||
|
4, 4, 4, 4, 4,
|
||||||
|
4, 4, 4, 4, 4,
|
||||||
|
4, 4, 4, 4, 4,
|
||||||
|
4, 4, 4, 4, 4,
|
||||||
|
})
|
||||||
|
|
||||||
|
-- Reset image for new test
|
||||||
|
array_to_pixels({
|
||||||
|
1, 1, 0, 0, 1,
|
||||||
|
1, 1, 0, 0, 1,
|
||||||
|
1, 0, 0, 0, 1,
|
||||||
|
1, 0, 0, 1, 1,
|
||||||
|
1, 0, 0, 1, 1,
|
||||||
|
}, i)
|
||||||
|
|
||||||
|
-- Select a centered 3x3 square
|
||||||
|
app.command.MaskAll {}
|
||||||
|
app.command.ModifySelection {
|
||||||
|
modifier = 'contract',
|
||||||
|
quantity = 1,
|
||||||
|
brush = 'square'
|
||||||
|
}
|
||||||
|
-- Intersect with current selection by color
|
||||||
|
app.command.MaskByColor {
|
||||||
|
color = Color{ index=1 },
|
||||||
|
tolerance = 0,
|
||||||
|
mode = SelectionMode.INTERSECT,
|
||||||
|
}
|
||||||
|
|
||||||
|
app.fgColor = Color{ index=2 }
|
||||||
|
app.command.Fill()
|
||||||
|
|
||||||
|
expect_img(i, {
|
||||||
|
1, 1, 0, 0, 1,
|
||||||
|
1, 2, 0, 0, 1,
|
||||||
|
1, 0, 0, 0, 1,
|
||||||
|
1, 0, 0, 2, 1,
|
||||||
|
1, 0, 0, 1, 1,
|
||||||
|
})
|
||||||
|
end
|
||||||
|
Loading…
x
Reference in New Issue
Block a user