Support horizontal/vertical symmetry at the same time (fix #1190)

This commit is contained in:
David Capello 2017-03-27 15:27:37 -03:00
parent 043489e532
commit 76df84491e
9 changed files with 131 additions and 76 deletions

View File

@ -79,6 +79,7 @@
<value id="NONE" value="0" />
<value id="HORIZONTAL" value="1" />
<value id="VERTICAL" value="2" />
<value id="BOTH" value="3" />
</enum>
<enum id="PaintingCursorType">
<value id="SIMPLE_CROSSHAIR" value="0" />

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 14 KiB

View File

@ -1,5 +1,5 @@
// Aseprite
// Copyright (C) 2015-2016 David Capello
// Copyright (C) 2015-2017 David Capello
//
// This program is distributed under the terms of
// the End-User License Agreement for Aseprite.
@ -52,5 +52,14 @@ void VerticalSymmetry::generateStrokes(const Stroke& mainStroke, Strokes& stroke
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

View File

@ -1,5 +1,5 @@
// Aseprite
// Copyright (C) 2015-2016 David Capello
// Copyright (C) 2015-2017 David Capello
//
// This program is distributed under the terms of
// the End-User License Agreement for Aseprite.
@ -10,6 +10,7 @@
#include "app/tools/stroke.h"
#include "app/tools/symmetry.h"
#include "base/unique_ptr.h"
namespace app {
namespace tools {
@ -32,6 +33,16 @@ 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:
base::UniquePtr<tools::Symmetry> m_a;
base::UniquePtr<tools::Symmetry> m_b;
};
} // namespace tools
} // namespace app

View File

@ -1305,17 +1305,17 @@ protected:
class ContextBar::SymmetryField : public ButtonSet {
public:
SymmetryField() : ButtonSet(3) {
SymmetryField() : ButtonSet(2) {
setMultipleSelection(true);
SkinTheme* theme = SkinTheme::instance();
addItem(theme->parts.noSymmetry());
addItem(theme->parts.horizontalSymmetry());
addItem(theme->parts.verticalSymmetry());
}
void setupTooltips(TooltipManager* tooltipManager) {
tooltipManager->addTooltipFor(at(0), "Without Symmetry", BOTTOM);
tooltipManager->addTooltipFor(at(1), "Horizontal Symmetry", BOTTOM);
tooltipManager->addTooltipFor(at(2), "Vertical Symmetry", BOTTOM);
tooltipManager->addTooltipFor(at(0), "Horizontal Symmetry", BOTTOM);
tooltipManager->addTooltipFor(at(1), "Vertical Symmetry", BOTTOM);
}
void updateWithCurrentDocument() {
@ -1325,7 +1325,8 @@ public:
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:
@ -1339,7 +1340,10 @@ private:
DocumentPreferences& docPref =
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
doc->notifyGeneralUpdate();

View File

@ -758,31 +758,25 @@ void Editor::drawSpriteUnclippedRect(ui::Graphics* g, const gfx::Rect& _rc)
if (isActive() &&
(m_flags & Editor::kShowSymmetryLine) &&
Preferences::instance().symmetryMode.enabled()) {
switch (m_docPref.symmetry.mode()) {
case app::gen::SymmetryMode::NONE:
// Do nothing
break;
case 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<double>(x),
enclosingRect.y,
enclosingRect.h);
}
break;
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<double>(x),
enclosingRect.y,
enclosingRect.h);
}
case 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<double>(y),
enclosingRect.w);
}
break;
}
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<double>(y),
enclosingRect.w);
}
}
}

View File

@ -297,19 +297,21 @@ bool StandbyState::onMouseDown(Editor* editor, MouseMessage* msg)
}
// Move symmetry
gfx::Rect box1, box2;
if (m_decorator->getSymmetryHandles(editor, box1, box2) &&
(box1.contains(msg->position()) ||
box2.contains(msg->position()))) {
auto& symmetry = Preferences::instance().document(editor->document()).symmetry;
auto mode = symmetry.mode();
bool horz = (mode == app::gen::SymmetryMode::HORIZONTAL);
auto& axis = (horz ? symmetry.xAxis:
symmetry.yAxis);
editor->setState(
EditorStatePtr(new MovingSymmetryState(editor, msg,
mode, axis)));
return true;
Decorator::Handles handles;
if (m_decorator->getSymmetryHandles(editor, handles)) {
for (const auto& handle : handles) {
if (handle.bounds.contains(msg->position())) {
auto mode = (handle.align & (TOP | BOTTOM) ? app::gen::SymmetryMode::HORIZONTAL:
app::gen::SymmetryMode::VERTICAL);
bool horz = (mode == app::gen::SymmetryMode::HORIZONTAL);
auto& symmetry = Preferences::instance().document(editor->document()).symmetry;
auto& axis = (horz ? symmetry.xAxis:
symmetry.yAxis);
editor->setState(
EditorStatePtr(new MovingSymmetryState(editor, msg, mode, axis)));
return true;
}
}
}
// Start the Tool-Loop
@ -785,19 +787,23 @@ bool StandbyState::Decorator::onSetCursor(tools::Ink* ink, Editor* editor, const
}
// Move symmetry
gfx::Rect box1, box2;
if (getSymmetryHandles(editor, box1, box2) &&
(box1.contains(mouseScreenPos) ||
box2.contains(mouseScreenPos))) {
switch (Preferences::instance().document(editor->document()).symmetry.mode()) {
case app::gen::SymmetryMode::HORIZONTAL:
editor->showMouseCursor(kSizeWECursor);
break;
case app::gen::SymmetryMode::VERTICAL:
editor->showMouseCursor(kSizeNSCursor);
break;
Handles handles;
if (getSymmetryHandles(editor, handles)) {
for (const auto& handle : handles) {
if (handle.bounds.contains(mouseScreenPos)) {
switch (handle.align) {
case TOP:
case BOTTOM:
editor->showMouseCursor(kSizeWECursor);
break;
case LEFT:
case RIGHT:
editor->showMouseCursor(kSizeNSCursor);
break;
}
return true;
}
}
return true;
}
return false;
@ -829,26 +835,26 @@ void StandbyState::Decorator::postRenderDecorator(EditorPostRender* render)
}
// Draw transformation handles (if the mask is visible and isn't frozen).
gfx::Rect box1, box2;
if (StandbyState::Decorator::getSymmetryHandles(editor, box1, box2)) {
Handles handles;
if (StandbyState::Decorator::getSymmetryHandles(editor, handles)) {
skin::SkinTheme* theme = static_cast<skin::SkinTheme*>(ui::get_theme());
she::Surface* part = theme->parts.transformationHandle()->bitmap(0);
ScreenGraphics g;
g.drawRgbaSurface(part, box1.x, box1.y);
g.drawRgbaSurface(part, box2.x, box2.y);
for (const auto& handle : handles)
g.drawRgbaSurface(part, handle.bounds.x, handle.bounds.y);
}
}
void StandbyState::Decorator::getInvalidDecoratoredRegion(Editor* editor, gfx::Region& region)
{
gfx::Rect box1, box2;
if (getSymmetryHandles(editor, box1, box2)) {
region.createUnion(region, gfx::Region(box1));
region.createUnion(region, gfx::Region(box2));
Handles handles;
if (getSymmetryHandles(editor, handles)) {
for (const auto& handle : handles)
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).
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;
auto mode = symmetry.mode();
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 editorViewport = gfx::RectF(View::getView(editor)->viewportBounds());
skin::SkinTheme* theme = static_cast<skin::SkinTheme*>(ui::get_theme());
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 = editor->editorToScreenF(pt1);
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());
pt1.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 = editor->editorToScreenF(pt1);
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());
pt1.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;
}
}

View File

@ -49,11 +49,19 @@ namespace app {
class Decorator : public EditorDecorator {
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);
virtual ~Decorator();
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);

View File

@ -165,6 +165,13 @@ public:
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;
}
}