mirror of
https://github.com/aseprite/aseprite.git
synced 2025-04-16 05:42:32 +00:00
Support horizontal/vertical symmetry at the same time (fix #1190)
This commit is contained in:
parent
043489e532
commit
76df84491e
@ -79,6 +79,7 @@
|
|||||||
<value id="NONE" value="0" />
|
<value id="NONE" value="0" />
|
||||||
<value id="HORIZONTAL" value="1" />
|
<value id="HORIZONTAL" value="1" />
|
||||||
<value id="VERTICAL" value="2" />
|
<value id="VERTICAL" value="2" />
|
||||||
|
<value id="BOTH" value="3" />
|
||||||
</enum>
|
</enum>
|
||||||
<enum id="PaintingCursorType">
|
<enum id="PaintingCursorType">
|
||||||
<value id="SIMPLE_CROSSHAIR" value="0" />
|
<value id="SIMPLE_CROSSHAIR" value="0" />
|
||||||
|
Binary file not shown.
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 14 KiB |
@ -1,5 +1,5 @@
|
|||||||
// Aseprite
|
// Aseprite
|
||||||
// Copyright (C) 2015-2016 David Capello
|
// Copyright (C) 2015-2017 David Capello
|
||||||
//
|
//
|
||||||
// This program is distributed under the terms of
|
// This program is distributed under the terms of
|
||||||
// the End-User License Agreement for Aseprite.
|
// the End-User License Agreement for Aseprite.
|
||||||
@ -52,5 +52,14 @@ void VerticalSymmetry::generateStrokes(const Stroke& mainStroke, Strokes& stroke
|
|||||||
strokes.push_back(stroke2);
|
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 tools
|
||||||
} // namespace app
|
} // namespace app
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
// Aseprite
|
// Aseprite
|
||||||
// Copyright (C) 2015-2016 David Capello
|
// Copyright (C) 2015-2017 David Capello
|
||||||
//
|
//
|
||||||
// This program is distributed under the terms of
|
// This program is distributed under the terms of
|
||||||
// the End-User License Agreement for Aseprite.
|
// the End-User License Agreement for Aseprite.
|
||||||
@ -10,6 +10,7 @@
|
|||||||
|
|
||||||
#include "app/tools/stroke.h"
|
#include "app/tools/stroke.h"
|
||||||
#include "app/tools/symmetry.h"
|
#include "app/tools/symmetry.h"
|
||||||
|
#include "base/unique_ptr.h"
|
||||||
|
|
||||||
namespace app {
|
namespace app {
|
||||||
namespace tools {
|
namespace tools {
|
||||||
@ -32,6 +33,16 @@ private:
|
|||||||
double m_y;
|
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:
|
||||||
|
base::UniquePtr<tools::Symmetry> m_a;
|
||||||
|
base::UniquePtr<tools::Symmetry> m_b;
|
||||||
|
};
|
||||||
|
|
||||||
} // namespace tools
|
} // namespace tools
|
||||||
} // namespace app
|
} // namespace app
|
||||||
|
|
||||||
|
@ -1305,17 +1305,17 @@ protected:
|
|||||||
|
|
||||||
class ContextBar::SymmetryField : public ButtonSet {
|
class ContextBar::SymmetryField : public ButtonSet {
|
||||||
public:
|
public:
|
||||||
SymmetryField() : ButtonSet(3) {
|
SymmetryField() : ButtonSet(2) {
|
||||||
|
setMultipleSelection(true);
|
||||||
|
|
||||||
SkinTheme* theme = SkinTheme::instance();
|
SkinTheme* theme = SkinTheme::instance();
|
||||||
addItem(theme->parts.noSymmetry());
|
|
||||||
addItem(theme->parts.horizontalSymmetry());
|
addItem(theme->parts.horizontalSymmetry());
|
||||||
addItem(theme->parts.verticalSymmetry());
|
addItem(theme->parts.verticalSymmetry());
|
||||||
}
|
}
|
||||||
|
|
||||||
void setupTooltips(TooltipManager* tooltipManager) {
|
void setupTooltips(TooltipManager* tooltipManager) {
|
||||||
tooltipManager->addTooltipFor(at(0), "Without Symmetry", BOTTOM);
|
tooltipManager->addTooltipFor(at(0), "Horizontal Symmetry", BOTTOM);
|
||||||
tooltipManager->addTooltipFor(at(1), "Horizontal Symmetry", BOTTOM);
|
tooltipManager->addTooltipFor(at(1), "Vertical Symmetry", BOTTOM);
|
||||||
tooltipManager->addTooltipFor(at(2), "Vertical Symmetry", BOTTOM);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void updateWithCurrentDocument() {
|
void updateWithCurrentDocument() {
|
||||||
@ -1325,7 +1325,8 @@ public:
|
|||||||
|
|
||||||
DocumentPreferences& docPref = Preferences::instance().document(doc);
|
DocumentPreferences& docPref = Preferences::instance().document(doc);
|
||||||
|
|
||||||
setSelectedItem((int)docPref.symmetry.mode());
|
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);
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
@ -1339,7 +1340,10 @@ private:
|
|||||||
DocumentPreferences& docPref =
|
DocumentPreferences& docPref =
|
||||||
Preferences::instance().document(doc);
|
Preferences::instance().document(doc);
|
||||||
|
|
||||||
docPref.symmetry.mode((app::gen::SymmetryMode)selectedItem());
|
int mode = 0;
|
||||||
|
if (at(0)->isSelected()) mode |= int(app::gen::SymmetryMode::HORIZONTAL);
|
||||||
|
if (at(1)->isSelected()) mode |= int(app::gen::SymmetryMode::VERTICAL);
|
||||||
|
docPref.symmetry.mode(app::gen::SymmetryMode(mode));
|
||||||
|
|
||||||
// Redraw symmetry rules
|
// Redraw symmetry rules
|
||||||
doc->notifyGeneralUpdate();
|
doc->notifyGeneralUpdate();
|
||||||
|
@ -758,31 +758,25 @@ void Editor::drawSpriteUnclippedRect(ui::Graphics* g, const gfx::Rect& _rc)
|
|||||||
if (isActive() &&
|
if (isActive() &&
|
||||||
(m_flags & Editor::kShowSymmetryLine) &&
|
(m_flags & Editor::kShowSymmetryLine) &&
|
||||||
Preferences::instance().symmetryMode.enabled()) {
|
Preferences::instance().symmetryMode.enabled()) {
|
||||||
switch (m_docPref.symmetry.mode()) {
|
int mode = int(m_docPref.symmetry.mode());
|
||||||
case app::gen::SymmetryMode::NONE:
|
if (mode & int(app::gen::SymmetryMode::HORIZONTAL)) {
|
||||||
// Do nothing
|
double x = m_docPref.symmetry.xAxis();
|
||||||
break;
|
if (x > 0) {
|
||||||
case app::gen::SymmetryMode::HORIZONTAL: {
|
gfx::Color color = color_utils::color_for_ui(m_docPref.grid.color());
|
||||||
double x = m_docPref.symmetry.xAxis();
|
g->drawVLine(color,
|
||||||
if (x > 0) {
|
spriteRect.x + m_proj.applyX<double>(x),
|
||||||
gfx::Color color = color_utils::color_for_ui(m_docPref.grid.color());
|
enclosingRect.y,
|
||||||
g->drawVLine(color,
|
enclosingRect.h);
|
||||||
spriteRect.x + m_proj.applyX<double>(x),
|
|
||||||
enclosingRect.y,
|
|
||||||
enclosingRect.h);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
case app::gen::SymmetryMode::VERTICAL: {
|
}
|
||||||
double y = m_docPref.symmetry.yAxis();
|
if (mode & int(app::gen::SymmetryMode::VERTICAL)) {
|
||||||
if (y > 0) {
|
double y = m_docPref.symmetry.yAxis();
|
||||||
gfx::Color color = color_utils::color_for_ui(m_docPref.grid.color());
|
if (y > 0) {
|
||||||
g->drawHLine(color,
|
gfx::Color color = color_utils::color_for_ui(m_docPref.grid.color());
|
||||||
enclosingRect.x,
|
g->drawHLine(color,
|
||||||
spriteRect.y + m_proj.applyY<double>(y),
|
enclosingRect.x,
|
||||||
enclosingRect.w);
|
spriteRect.y + m_proj.applyY<double>(y),
|
||||||
}
|
enclosingRect.w);
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -297,19 +297,21 @@ bool StandbyState::onMouseDown(Editor* editor, MouseMessage* msg)
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Move symmetry
|
// Move symmetry
|
||||||
gfx::Rect box1, box2;
|
Decorator::Handles handles;
|
||||||
if (m_decorator->getSymmetryHandles(editor, box1, box2) &&
|
if (m_decorator->getSymmetryHandles(editor, handles)) {
|
||||||
(box1.contains(msg->position()) ||
|
for (const auto& handle : handles) {
|
||||||
box2.contains(msg->position()))) {
|
if (handle.bounds.contains(msg->position())) {
|
||||||
auto& symmetry = Preferences::instance().document(editor->document()).symmetry;
|
auto mode = (handle.align & (TOP | BOTTOM) ? app::gen::SymmetryMode::HORIZONTAL:
|
||||||
auto mode = symmetry.mode();
|
app::gen::SymmetryMode::VERTICAL);
|
||||||
bool horz = (mode == app::gen::SymmetryMode::HORIZONTAL);
|
bool horz = (mode == app::gen::SymmetryMode::HORIZONTAL);
|
||||||
auto& axis = (horz ? symmetry.xAxis:
|
auto& symmetry = Preferences::instance().document(editor->document()).symmetry;
|
||||||
symmetry.yAxis);
|
auto& axis = (horz ? symmetry.xAxis:
|
||||||
editor->setState(
|
symmetry.yAxis);
|
||||||
EditorStatePtr(new MovingSymmetryState(editor, msg,
|
editor->setState(
|
||||||
mode, axis)));
|
EditorStatePtr(new MovingSymmetryState(editor, msg, mode, axis)));
|
||||||
return true;
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Start the Tool-Loop
|
// Start the Tool-Loop
|
||||||
@ -785,19 +787,23 @@ bool StandbyState::Decorator::onSetCursor(tools::Ink* ink, Editor* editor, const
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Move symmetry
|
// Move symmetry
|
||||||
gfx::Rect box1, box2;
|
Handles handles;
|
||||||
if (getSymmetryHandles(editor, box1, box2) &&
|
if (getSymmetryHandles(editor, handles)) {
|
||||||
(box1.contains(mouseScreenPos) ||
|
for (const auto& handle : handles) {
|
||||||
box2.contains(mouseScreenPos))) {
|
if (handle.bounds.contains(mouseScreenPos)) {
|
||||||
switch (Preferences::instance().document(editor->document()).symmetry.mode()) {
|
switch (handle.align) {
|
||||||
case app::gen::SymmetryMode::HORIZONTAL:
|
case TOP:
|
||||||
editor->showMouseCursor(kSizeWECursor);
|
case BOTTOM:
|
||||||
break;
|
editor->showMouseCursor(kSizeWECursor);
|
||||||
case app::gen::SymmetryMode::VERTICAL:
|
break;
|
||||||
editor->showMouseCursor(kSizeNSCursor);
|
case LEFT:
|
||||||
break;
|
case RIGHT:
|
||||||
|
editor->showMouseCursor(kSizeNSCursor);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
@ -829,26 +835,26 @@ void StandbyState::Decorator::postRenderDecorator(EditorPostRender* render)
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Draw transformation handles (if the mask is visible and isn't frozen).
|
// Draw transformation handles (if the mask is visible and isn't frozen).
|
||||||
gfx::Rect box1, box2;
|
Handles handles;
|
||||||
if (StandbyState::Decorator::getSymmetryHandles(editor, box1, box2)) {
|
if (StandbyState::Decorator::getSymmetryHandles(editor, handles)) {
|
||||||
skin::SkinTheme* theme = static_cast<skin::SkinTheme*>(ui::get_theme());
|
skin::SkinTheme* theme = static_cast<skin::SkinTheme*>(ui::get_theme());
|
||||||
she::Surface* part = theme->parts.transformationHandle()->bitmap(0);
|
she::Surface* part = theme->parts.transformationHandle()->bitmap(0);
|
||||||
ScreenGraphics g;
|
ScreenGraphics g;
|
||||||
g.drawRgbaSurface(part, box1.x, box1.y);
|
for (const auto& handle : handles)
|
||||||
g.drawRgbaSurface(part, box2.x, box2.y);
|
g.drawRgbaSurface(part, handle.bounds.x, handle.bounds.y);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void StandbyState::Decorator::getInvalidDecoratoredRegion(Editor* editor, gfx::Region& region)
|
void StandbyState::Decorator::getInvalidDecoratoredRegion(Editor* editor, gfx::Region& region)
|
||||||
{
|
{
|
||||||
gfx::Rect box1, box2;
|
Handles handles;
|
||||||
if (getSymmetryHandles(editor, box1, box2)) {
|
if (getSymmetryHandles(editor, handles)) {
|
||||||
region.createUnion(region, gfx::Region(box1));
|
for (const auto& handle : handles)
|
||||||
region.createUnion(region, gfx::Region(box2));
|
region.createUnion(region, gfx::Region(handle.bounds));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool StandbyState::Decorator::getSymmetryHandles(Editor* editor, gfx::Rect& box1, gfx::Rect& box2)
|
bool StandbyState::Decorator::getSymmetryHandles(Editor* editor, Handles& handles)
|
||||||
{
|
{
|
||||||
// Draw transformation handles (if the mask is visible and isn't frozen).
|
// Draw transformation handles (if the mask is visible and isn't frozen).
|
||||||
if (editor->isActive() &&
|
if (editor->isActive() &&
|
||||||
@ -857,16 +863,15 @@ bool StandbyState::Decorator::getSymmetryHandles(Editor* editor, gfx::Rect& box1
|
|||||||
const auto& symmetry = Preferences::instance().document(editor->document()).symmetry;
|
const auto& symmetry = Preferences::instance().document(editor->document()).symmetry;
|
||||||
auto mode = symmetry.mode();
|
auto mode = symmetry.mode();
|
||||||
if (mode != app::gen::SymmetryMode::NONE) {
|
if (mode != app::gen::SymmetryMode::NONE) {
|
||||||
bool horz = (mode == app::gen::SymmetryMode::HORIZONTAL);
|
|
||||||
double pos = (horz ? symmetry.xAxis():
|
|
||||||
symmetry.yAxis());
|
|
||||||
gfx::RectF spriteBounds = gfx::RectF(editor->sprite()->bounds());
|
gfx::RectF spriteBounds = gfx::RectF(editor->sprite()->bounds());
|
||||||
gfx::RectF editorViewport = gfx::RectF(View::getView(editor)->viewportBounds());
|
gfx::RectF editorViewport = gfx::RectF(View::getView(editor)->viewportBounds());
|
||||||
skin::SkinTheme* theme = static_cast<skin::SkinTheme*>(ui::get_theme());
|
skin::SkinTheme* theme = static_cast<skin::SkinTheme*>(ui::get_theme());
|
||||||
she::Surface* part = theme->parts.transformationHandle()->bitmap(0);
|
she::Surface* part = theme->parts.transformationHandle()->bitmap(0);
|
||||||
gfx::PointF pt1, pt2;
|
|
||||||
|
|
||||||
if (horz) {
|
if (int(mode) & int(app::gen::SymmetryMode::HORIZONTAL)) {
|
||||||
|
double pos = symmetry.xAxis();
|
||||||
|
gfx::PointF pt1, pt2;
|
||||||
|
|
||||||
pt1 = gfx::PointF(spriteBounds.x+pos, spriteBounds.y);
|
pt1 = gfx::PointF(spriteBounds.x+pos, spriteBounds.y);
|
||||||
pt1 = editor->editorToScreenF(pt1);
|
pt1 = editor->editorToScreenF(pt1);
|
||||||
pt2 = gfx::PointF(spriteBounds.x+pos, spriteBounds.y+spriteBounds.h);
|
pt2 = gfx::PointF(spriteBounds.x+pos, spriteBounds.y+spriteBounds.h);
|
||||||
@ -875,8 +880,19 @@ bool StandbyState::Decorator::getSymmetryHandles(Editor* editor, gfx::Rect& box1
|
|||||||
pt2.y = std::min(pt2.y, editorViewport.point2().y-part->height());
|
pt2.y = std::min(pt2.y, editorViewport.point2().y-part->height());
|
||||||
pt1.x -= part->width()/2;
|
pt1.x -= part->width()/2;
|
||||||
pt2.x -= part->width()/2;
|
pt2.x -= part->width()/2;
|
||||||
|
|
||||||
|
handles.push_back(
|
||||||
|
Handle(TOP,
|
||||||
|
gfx::Rect(pt1.x, pt1.y, part->width(), part->height())));
|
||||||
|
handles.push_back(
|
||||||
|
Handle(BOTTOM,
|
||||||
|
gfx::Rect(pt2.x, pt2.y, part->width(), part->height())));
|
||||||
}
|
}
|
||||||
else {
|
|
||||||
|
if (int(mode) & int(app::gen::SymmetryMode::VERTICAL)) {
|
||||||
|
double pos = symmetry.yAxis();
|
||||||
|
gfx::PointF pt1, pt2;
|
||||||
|
|
||||||
pt1 = gfx::PointF(spriteBounds.x, spriteBounds.y+pos);
|
pt1 = gfx::PointF(spriteBounds.x, spriteBounds.y+pos);
|
||||||
pt1 = editor->editorToScreenF(pt1);
|
pt1 = editor->editorToScreenF(pt1);
|
||||||
pt2 = gfx::PointF(spriteBounds.x+spriteBounds.w, spriteBounds.y+pos);
|
pt2 = gfx::PointF(spriteBounds.x+spriteBounds.w, spriteBounds.y+pos);
|
||||||
@ -885,10 +901,15 @@ bool StandbyState::Decorator::getSymmetryHandles(Editor* editor, gfx::Rect& box1
|
|||||||
pt2.x = std::min(pt2.x, editorViewport.point2().x-part->width());
|
pt2.x = std::min(pt2.x, editorViewport.point2().x-part->width());
|
||||||
pt1.y -= part->height()/2;
|
pt1.y -= part->height()/2;
|
||||||
pt2.y -= part->height()/2;
|
pt2.y -= part->height()/2;
|
||||||
|
|
||||||
|
handles.push_back(
|
||||||
|
Handle(LEFT,
|
||||||
|
gfx::Rect(pt1.x, pt1.y, part->width(), part->height())));
|
||||||
|
handles.push_back(
|
||||||
|
Handle(RIGHT,
|
||||||
|
gfx::Rect(pt2.x, pt2.y, part->width(), part->height())));
|
||||||
}
|
}
|
||||||
|
|
||||||
box1 = gfx::Rect(pt1.x, pt1.y, part->width(), part->height());
|
|
||||||
box2 = gfx::Rect(pt2.x, pt2.y, part->width(), part->height());
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -49,11 +49,19 @@ namespace app {
|
|||||||
|
|
||||||
class Decorator : public EditorDecorator {
|
class Decorator : public EditorDecorator {
|
||||||
public:
|
public:
|
||||||
|
struct Handle {
|
||||||
|
int align;
|
||||||
|
gfx::Rect bounds;
|
||||||
|
Handle(int align, const gfx::Rect& bounds)
|
||||||
|
: align(align), bounds(bounds) { }
|
||||||
|
};
|
||||||
|
typedef std::vector<Handle> Handles;
|
||||||
|
|
||||||
Decorator(StandbyState* standbyState);
|
Decorator(StandbyState* standbyState);
|
||||||
virtual ~Decorator();
|
virtual ~Decorator();
|
||||||
|
|
||||||
TransformHandles* getTransformHandles(Editor* editor);
|
TransformHandles* getTransformHandles(Editor* editor);
|
||||||
bool getSymmetryHandles(Editor* editor, gfx::Rect& box1, gfx::Rect& box2);
|
bool getSymmetryHandles(Editor* editor, Handles& handles);
|
||||||
|
|
||||||
bool onSetCursor(tools::Ink* ink, Editor* editor, const gfx::Point& mouseScreenPos);
|
bool onSetCursor(tools::Ink* ink, Editor* editor, const gfx::Point& mouseScreenPos);
|
||||||
|
|
||||||
|
@ -165,6 +165,13 @@ public:
|
|||||||
case app::gen::SymmetryMode::VERTICAL:
|
case app::gen::SymmetryMode::VERTICAL:
|
||||||
m_symmetry.reset(new app::tools::VerticalSymmetry(m_docPref.symmetry.yAxis()));
|
m_symmetry.reset(new app::tools::VerticalSymmetry(m_docPref.symmetry.yAxis()));
|
||||||
break;
|
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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user