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="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

View File

@ -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

View File

@ -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

View File

@ -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();

View File

@ -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;
} }
} }
} }

View File

@ -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;
} }
} }

View File

@ -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);

View File

@ -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;
} }
} }