Fix symmetry button is kept pressed when we didn't pressed (fix #4760)

This fix removes the 'non sense symmetry filter' to prevent
some buttons from being unintentionally held down.

Moved the drawing process for symmetry axes from
'Editor::drawSpriteUnclippedRect' to
'Editor::drawOneSpriteUnclippedRect' to allow semi-transparent axes.
This also produces axes on every tile in tile mode.

Pixel ratios other than 1:1 are now considered in the drawing logic of
diagonal axes.
This commit is contained in:
Gaspar Capello 2025-02-26 15:55:12 -03:00
parent 5739bfe287
commit 7ef4fa0ad0
5 changed files with 117 additions and 147 deletions

View File

@ -1,5 +1,5 @@
// Aseprite
// Copyright (C) 2021-2024 Igara Studio S.A.
// Copyright (C) 2021-2025 Igara Studio S.A.
//
// This program is distributed under the terms of
// the End-User License Agreement for Aseprite.
@ -20,7 +20,7 @@ void Symmetry::generateStrokes(const Stroke& stroke, Strokes& strokes, ToolLoop*
{
Stroke stroke2;
strokes.push_back(stroke);
gen::SymmetryMode symmetryMode = loop->getSymmetry()->mode();
const gen::SymmetryMode symmetryMode = tools::Symmetry::resolveMode(loop->getSymmetry()->mode());
switch (symmetryMode) {
case gen::SymmetryMode::NONE: ASSERT(false); break;
@ -171,4 +171,14 @@ void Symmetry::calculateSymmetricalStroke(const Stroke& refStroke,
}
}
gen::SymmetryMode Symmetry::resolveMode(gen::SymmetryMode mode)
{
return (((int(mode) & int(gen::SymmetryMode::HORIZONTAL)) ||
(int(mode) & int(gen::SymmetryMode::VERTICAL))) &&
((int(mode) & int(gen::SymmetryMode::RIGHT_DIAG)) ||
(int(mode) & int(gen::SymmetryMode::LEFT_DIAG)))) ?
gen::SymmetryMode::ALL :
mode;
}
}} // namespace app::tools

View File

@ -1,5 +1,5 @@
// Aseprite
// Copyright (C) 2021-2024 Igara Studio S.A.
// Copyright (C) 2021-2025 Igara Studio S.A.
// Copyright (C) 2015 David Capello
//
// This program is distributed under the terms of
@ -38,6 +38,8 @@ public:
gen::SymmetryMode mode() const { return m_symmetryMode; }
static gen::SymmetryMode resolveMode(gen::SymmetryMode mode);
private:
void calculateSymmetricalStroke(const Stroke& refStroke,
Stroke& stroke,

View File

@ -1,5 +1,5 @@
// Aseprite
// Copyright (C) 2018-2024 Igara Studio S.A.
// Copyright (C) 2018-2025 Igara Studio S.A.
// Copyright (C) 2001-2018 David Capello
//
// This program is distributed under the terms of
@ -1530,14 +1530,10 @@ public:
DocumentPreferences& docPref = Preferences::instance().document(doc);
at(0)->setSelected(
int(docPref.symmetry.mode()) & int(app::gen::SymmetryMode::HORIZONTAL) ? true : false);
at(1)->setSelected(
int(docPref.symmetry.mode()) & int(app::gen::SymmetryMode::VERTICAL) ? true : false);
at(2)->setSelected(
int(docPref.symmetry.mode()) & int(app::gen::SymmetryMode::RIGHT_DIAG) ? true : false);
at(3)->setSelected(
int(docPref.symmetry.mode()) & int(app::gen::SymmetryMode::LEFT_DIAG) ? true : false);
at(0)->setSelected(int(docPref.symmetry.mode()) & int(app::gen::SymmetryMode::HORIZONTAL));
at(1)->setSelected(int(docPref.symmetry.mode()) & int(app::gen::SymmetryMode::VERTICAL));
at(2)->setSelected(int(docPref.symmetry.mode()) & int(app::gen::SymmetryMode::RIGHT_DIAG));
at(3)->setSelected(int(docPref.symmetry.mode()) & int(app::gen::SymmetryMode::LEFT_DIAG));
}
private:
@ -1551,60 +1547,7 @@ private:
DocumentPreferences& docPref = Preferences::instance().document(doc);
auto oldMode = docPref.symmetry.mode();
int mode = 0;
if (at(0)->isSelected())
mode |= int(app::gen::SymmetryMode::HORIZONTAL);
if (at(1)->isSelected())
mode |= int(app::gen::SymmetryMode::VERTICAL);
if (at(2)->isSelected())
mode |= int(app::gen::SymmetryMode::RIGHT_DIAG);
if (at(3)->isSelected())
mode |= int(app::gen::SymmetryMode::LEFT_DIAG);
// Non sense symmetries filter:
// - H + 1Diag
// - V + 1Diag
// - H + V + 1Diag
const bool HorV = (mode & int(app::gen::SymmetryMode::HORIZONTAL)) ||
(mode & int(app::gen::SymmetryMode::VERTICAL));
const bool HxorV = !(mode & int(app::gen::SymmetryMode::HORIZONTAL)) !=
!(mode & int(app::gen::SymmetryMode::VERTICAL));
const bool RDxorLD = !(mode & int(app::gen::SymmetryMode::RIGHT_DIAG)) !=
!(mode & int(app::gen::SymmetryMode::LEFT_DIAG));
if (oldMode == gen::SymmetryMode::HORIZONTAL || oldMode == gen::SymmetryMode::VERTICAL ||
oldMode == gen::SymmetryMode::BOTH) {
if (HorV && RDxorLD) {
mode = int(app::gen::SymmetryMode::ALL);
at(0)->setSelected(true);
at(1)->setSelected(true);
at(2)->setSelected(true);
at(3)->setSelected(true);
}
}
else if (oldMode == gen::SymmetryMode::ALL) {
if (HxorV) {
mode = int(app::gen::SymmetryMode::BOTH_DIAG);
at(0)->setSelected(false);
at(1)->setSelected(false);
}
else if (RDxorLD) {
mode = int(app::gen::SymmetryMode::BOTH);
at(2)->setSelected(false);
at(3)->setSelected(false);
}
}
else if ((oldMode == gen::SymmetryMode::RIGHT_DIAG || oldMode == gen::SymmetryMode::LEFT_DIAG ||
oldMode == gen::SymmetryMode::BOTH_DIAG) &&
HorV) {
mode = int(app::gen::SymmetryMode::ALL);
at(0)->setSelected(true);
at(1)->setSelected(true);
at(2)->setSelected(true);
at(3)->setSelected(true);
}
// Non sense symmetries filter end
if (at(0)->isSelected())
mode |= int(app::gen::SymmetryMode::HORIZONTAL);
if (at(1)->isSelected())

View File

@ -1,5 +1,5 @@
// Aseprite
// Copyright (C) 2018-2024 Igara Studio S.A.
// Copyright (C) 2018-2025 Igara Studio S.A.
// Copyright (C) 2001-2018 David Capello
//
// This program is distributed under the terms of
@ -31,6 +31,7 @@
#include "app/tools/active_tool.h"
#include "app/tools/controller.h"
#include "app/tools/ink.h"
#include "app/tools/symmetry.h"
#include "app/tools/tool.h"
#include "app/tools/tool_box.h"
#include "app/ui/color_bar.h"
@ -831,6 +832,86 @@ void Editor::drawOneSpriteUnclippedRect(ui::Graphics* g,
m_docPref.grid.forceSection();
}
m_docPref.show.grid.forceDirtyFlag();
// Symmetry mode
if (isActive() && (m_flags & Editor::kShowSymmetryLine) &&
Preferences::instance().symmetryMode.enabled()) {
const int symmetryButtons = int(m_docPref.symmetry.mode());
// Symmetry::resolveMode is to calculate the right symmetry
// mode. This is necessary because some symmetry settings
// do not make sense and should be forced to 'ALL'
const int mode = int(tools::Symmetry::resolveMode(m_docPref.symmetry.mode()));
const gfx::Color color = color_utils::color_for_ui(m_docPref.grid.color());
const gfx::Color semiTransparentColor =
gfx::rgba(rgba_getr(color), rgba_getg(color), rgba_getb(color), rgba_geta(color) / 4);
const double x = int(m_proj.applyX<double>(m_docPref.symmetry.xAxis()));
const double y = int(m_proj.applyY<double>(m_docPref.symmetry.yAxis()));
if (mode & int(app::gen::SymmetryMode::HORIZONTAL) && x > 0) {
g->drawVLine(symmetryButtons & int(app::gen::SymmetryMode::HORIZONTAL) ?
color :
semiTransparentColor,
enclosingRect.x + x,
enclosingRect.y,
enclosingRect.h);
}
if (mode & int(app::gen::SymmetryMode::VERTICAL) && y > 0) {
g->drawHLine(
symmetryButtons & int(app::gen::SymmetryMode::VERTICAL) ? color : semiTransparentColor,
enclosingRect.x,
enclosingRect.y + y,
enclosingRect.w);
}
if (mode & int(app::gen::SymmetryMode::RIGHT_DIAG)) {
// Bottom point intersection:
gfx::Point bottomLeft(
enclosingRect.x + x + m_proj.turnYinTermsOfX<int>(y - enclosingRect.h),
enclosingRect.y2());
if (bottomLeft.x < enclosingRect.x) {
// Left intersection
bottomLeft.y = enclosingRect.y2() +
m_proj.turnXinTermsOfY<int>(bottomLeft.x - enclosingRect.x);
bottomLeft.x = enclosingRect.x;
}
// Top intersection
gfx::Point topRight(enclosingRect.x + x + m_proj.turnYinTermsOfX<int>(y),
enclosingRect.y);
if (enclosingRect.x2() < topRight.x) {
// Right intersection
topRight.y = enclosingRect.y +
m_proj.applyY<int>(m_proj.removeX<int>(topRight.x - enclosingRect.x2()));
topRight.x = enclosingRect.x2();
}
g->drawLine(symmetryButtons & int(app::gen::SymmetryMode::RIGHT_DIAG) ?
color :
semiTransparentColor,
bottomLeft,
topRight);
}
if (mode & int(app::gen::SymmetryMode::LEFT_DIAG)) {
// Bottom point intersection:
gfx::Point bottomRight(
enclosingRect.x + x + m_proj.turnYinTermsOfX<int>(enclosingRect.h - y),
enclosingRect.y2());
if (enclosingRect.x2() < bottomRight.x) {
// Left intersection
bottomRight.y = enclosingRect.y2() +
m_proj.turnXinTermsOfY<int>(enclosingRect.x2() - bottomRight.x);
bottomRight.x = enclosingRect.x2();
}
// Top intersection
gfx::Point topLeft(enclosingRect.x + x - m_proj.turnYinTermsOfX<int>(y), enclosingRect.y);
if (topLeft.x < enclosingRect.x) {
// Right intersection
topLeft.y = enclosingRect.y + m_proj.turnXinTermsOfY<int>(enclosingRect.x - topLeft.x);
topLeft.x = enclosingRect.x;
}
g->drawLine(
symmetryButtons & int(app::gen::SymmetryMode::LEFT_DIAG) ? color : semiTransparentColor,
topLeft,
bottomRight);
}
}
}
}
}
@ -907,86 +988,6 @@ void Editor::drawSpriteUnclippedRect(ui::Graphics* g, const gfx::Rect& _rc)
if (m_docPref.show.slices())
drawSlices(g);
// Symmetry mode
if (isActive() && (m_flags & Editor::kShowSymmetryLine) &&
Preferences::instance().symmetryMode.enabled()) {
int mode = int(m_docPref.symmetry.mode());
if (mode & int(app::gen::SymmetryMode::HORIZONTAL)) {
double x = m_docPref.symmetry.xAxis();
if (x > 0) {
gfx::Color color = color_utils::color_for_ui(m_docPref.grid.color());
g->drawVLine(
color,
spriteRect.x + m_proj.applyX(mainTilePosition().x) + int(m_proj.applyX<double>(x)),
enclosingRect.y,
enclosingRect.h);
}
}
if (mode & int(app::gen::SymmetryMode::VERTICAL)) {
double y = m_docPref.symmetry.yAxis();
if (y > 0) {
gfx::Color color = color_utils::color_for_ui(m_docPref.grid.color());
g->drawHLine(
color,
enclosingRect.x,
spriteRect.y + m_proj.applyY(mainTilePosition().y) + int(m_proj.applyY<double>(y)),
enclosingRect.w);
}
}
if (mode & int(app::gen::SymmetryMode::RIGHT_DIAG)) {
double y = m_docPref.symmetry.yAxis();
double x = m_docPref.symmetry.xAxis();
gfx::Color color = color_utils::color_for_ui(m_docPref.grid.color());
// Bottom point intersection:
gfx::Point bottomLeft(
enclosingRect.x + m_proj.applyY(mainTilePosition().x) + int(m_proj.applyX<double>(x)) -
(enclosingRect.h - m_proj.applyY(mainTilePosition().y) - int(m_proj.applyY<double>(y))),
enclosingRect.y2());
if (bottomLeft.x < enclosingRect.x) {
// Left intersection
bottomLeft.y = enclosingRect.y2() - enclosingRect.x + bottomLeft.x;
bottomLeft.x = enclosingRect.x;
}
// Top intersection
gfx::Point topRight(enclosingRect.x + m_proj.applyY(mainTilePosition().x) +
int(m_proj.applyX<double>(x)) + m_proj.applyY(mainTilePosition().y) +
int(m_proj.applyY<double>(y)),
enclosingRect.y);
if (enclosingRect.x2() < topRight.x) {
// Right intersection
topRight.y = enclosingRect.y + topRight.x - enclosingRect.x2();
topRight.x = enclosingRect.x2();
}
g->drawLine(color, bottomLeft, topRight);
}
if (mode & int(app::gen::SymmetryMode::LEFT_DIAG)) {
double y = m_docPref.symmetry.yAxis();
double x = m_docPref.symmetry.xAxis();
gfx::Color color = color_utils::color_for_ui(m_docPref.grid.color());
// Bottom point intersection:
gfx::Point bottomRight(
enclosingRect.x + m_proj.applyY(mainTilePosition().x) + int(m_proj.applyX<double>(x)) +
(enclosingRect.h - m_proj.applyY(mainTilePosition().y) - int(m_proj.applyX<double>(y))),
enclosingRect.y2());
if (enclosingRect.x2() < bottomRight.x) {
// Left intersection
bottomRight.y = enclosingRect.y2() - bottomRight.x + enclosingRect.x2();
bottomRight.x = enclosingRect.x2();
}
// Top intersection
gfx::Point topLeft(enclosingRect.x + m_proj.applyY(mainTilePosition().x) +
int(m_proj.applyX<double>(x)) - m_proj.applyY(mainTilePosition().y) -
int(m_proj.applyY<double>(y)),
enclosingRect.y);
if (topLeft.x < enclosingRect.x) {
// Right intersection
topLeft.y = enclosingRect.y + enclosingRect.x - topLeft.x;
topLeft.x = enclosingRect.x;
}
g->drawLine(color, topLeft, bottomRight);
}
}
// Draw active layer/cel edges
if ((m_docPref.show.layerEdges() || m_showAutoCelGuides) &&
// Show layer edges and possibly cel guides only on states that

View File

@ -1,5 +1,5 @@
// Aseprite Render Library
// Copyright (c) 2020 Igara Studio S.A.
// Copyright (c) 2020-2025 Igara Studio S.A.
// Copyright (c) 2016 David Capello
//
// This file is released under the terms of the MIT license.
@ -70,6 +70,20 @@ public:
return T(m_zoom.removeCeiling(y)) / T(m_pixelRatio.h);
}
// Used in 'editor.cpp' to do some math between x,y values.
// Useful for calculating diagonal symmetry axis positions when pixel ratio is other than 1:1
template<typename T>
T turnXinTermsOfY(T x) const
{
return x * T(m_pixelRatio.h) / T(m_pixelRatio.w);
}
template<typename T>
T turnYinTermsOfX(T y) const
{
return y * T(m_pixelRatio.w) / T(m_pixelRatio.h);
}
gfx::Rect apply(const gfx::Rect& r) const
{
int u = applyX(r.x);