Add MovingSymmetryState to modify symmetry axis position

This commit is contained in:
David Capello 2015-10-28 20:00:18 -03:00
parent 7c8876dd65
commit a3deb2063e
9 changed files with 321 additions and 88 deletions

View File

@ -344,6 +344,7 @@ add_library(app-lib
ui/editor/editor_view.cpp
ui/editor/moving_cel_state.cpp
ui/editor/moving_pixels_state.cpp
ui/editor/moving_symmetry_state.cpp
ui/editor/navigate_state.cpp
ui/editor/pivot_helpers.cpp
ui/editor/pixels_movement.cpp

View File

@ -15,6 +15,7 @@
#include "app/resource_finder.h"
#include "app/tools/ink.h"
#include "app/tools/tool.h"
#include "doc/sprite.h"
namespace app {
@ -92,23 +93,27 @@ ToolPreferences& Preferences::tool(tools::Tool* tool)
}
}
DocumentPreferences& Preferences::document(const app::Document* document)
DocumentPreferences& Preferences::document(const app::Document* doc)
{
auto it = m_docs.find(document);
auto it = m_docs.find(doc);
if (it != m_docs.end()) {
return *it->second;
}
else {
DocumentPreferences* docPref;
if (document) {
if (doc) {
docPref = new DocumentPreferences("");
*docPref = this->document(nullptr);
// Default values for symmetry
docPref->symmetry.xAxis.setDefaultValue(doc->sprite()->width()/2);
docPref->symmetry.yAxis.setDefaultValue(doc->sprite()->height()/2);
}
else
docPref = new DocumentPreferences("");
m_docs[document] = docPref;
serializeDocPref(document, docPref, false);
m_docs[doc] = docPref;
serializeDocPref(doc, docPref, false);
return *docPref;
}
}
@ -151,18 +156,21 @@ void Preferences::serializeDocPref(const app::Document* doc, app::DocumentPrefer
bool specific_file = false;
if (doc) {
if (!doc->isAssociatedToFile())
if (doc->isAssociatedToFile()) {
push_config_state();
set_config_file(docConfigFileName(doc).c_str());
specific_file = true;
}
else if (save)
return;
push_config_state();
set_config_file(docConfigFileName(doc).c_str());
specific_file = true;
}
if (save)
docPref->save();
else
else {
// Load default preferences, or preferences from .ini file.
docPref->load();
}
if (specific_file) {
flush_config_file();

View File

@ -633,7 +633,7 @@ void Editor::drawSpriteUnclippedRect(ui::Graphics* g, const gfx::Rect& _rc)
}
// Symmetry mode
{
if (Preferences::instance().symmetryMode.enabled()) {
switch (docPref.symmetry.mode()) {
case app::gen::SymmetryMode::NONE:
// Do nothing

View File

@ -68,8 +68,13 @@ namespace app {
kShowOnionskin = 4,
kShowOutside = 8,
kShowDecorators = 16,
kDefaultEditorFlags = (kShowGrid | kShowMask |
kShowOnionskin | kShowOutside | kShowDecorators),
kShowSymmetryLine = 2,
kDefaultEditorFlags = (kShowGrid |
kShowMask |
kShowOnionskin |
kShowOutside |
kShowDecorators |
kShowSymmetryLine)
};
enum class ZoomBehavior {

View File

@ -0,0 +1,83 @@
// Aseprite
// Copyright (C) 2015 David Capello
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License version 2 as
// published by the Free Software Foundation.
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include "app/ui/editor/moving_symmetry_state.h"
#include "app/ui/editor/editor.h"
#include "app/ui/status_bar.h"
#include "ui/message.h"
namespace app {
using namespace ui;
MovingSymmetryState::MovingSymmetryState(Editor* editor, MouseMessage* msg,
app::gen::SymmetryMode mode,
Option<int>& symmetryAxis)
: m_symmetryMode(mode)
, m_symmetryAxis(symmetryAxis)
, m_symmetryAxisStart(symmetryAxis())
{
m_mouseStart = editor->screenToEditor(msg->position());
editor->captureMouse();
}
MovingSymmetryState::~MovingSymmetryState()
{
}
bool MovingSymmetryState::onMouseUp(Editor* editor, MouseMessage* msg)
{
editor->backToPreviousState();
editor->releaseMouse();
return true;
}
bool MovingSymmetryState::onMouseMove(Editor* editor, MouseMessage* msg)
{
gfx::Point newCursorPos = editor->screenToEditor(msg->position());
gfx::Point delta = newCursorPos - m_mouseStart;
int pos = 0;
switch (m_symmetryMode) {
case app::gen::SymmetryMode::HORIZONTAL:
pos = m_symmetryAxisStart + delta.x;
pos = MID(1, pos, editor->sprite()->width()-1);
break;
case app::gen::SymmetryMode::VERTICAL:
pos = m_symmetryAxisStart + delta.y;
pos = MID(1, pos, editor->sprite()->height()-1);
break;
}
m_symmetryAxis(pos);
// Redraw the editor.
editor->invalidate();
// Use StandbyState implementation
return StandbyState::onMouseMove(editor, msg);
}
bool MovingSymmetryState::onUpdateStatusBar(Editor* editor)
{
if (m_symmetryMode == app::gen::SymmetryMode::HORIZONTAL)
StatusBar::instance()->setStatusText
(0, "Left %3d Right %3d", m_symmetryAxis(),
editor->sprite()->width() - m_symmetryAxis());
else
StatusBar::instance()->setStatusText
(0, "Top %3d Bottom %3d", m_symmetryAxis(),
editor->sprite()->height() - m_symmetryAxis());
return true;
}
} // namespace app

View File

@ -0,0 +1,41 @@
// Aseprite
// Copyright (C) 2015 David Capello
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License version 2 as
// published by the Free Software Foundation.
#ifndef APP_UI_EDITOR_MOVING_SYMMETRY_STATE_H_INCLUDED
#define APP_UI_EDITOR_MOVING_SYMMETRY_STATE_H_INCLUDED
#pragma once
#include "app/pref/preferences.h"
#include "app/ui/editor/standby_state.h"
namespace app {
class Editor;
class MovingSymmetryState : public StandbyState {
public:
MovingSymmetryState(Editor* editor, ui::MouseMessage* msg,
app::gen::SymmetryMode mode,
Option<int>& symmetryAxis);
virtual ~MovingSymmetryState();
virtual bool onMouseUp(Editor* editor, ui::MouseMessage* msg) override;
virtual bool onMouseMove(Editor* editor, ui::MouseMessage* msg) override;
virtual bool onUpdateStatusBar(Editor* editor) override;
virtual bool requireBrushPreview() override { return false; }
private:
app::gen::SymmetryMode m_symmetryMode;
Option<int>& m_symmetryAxis;
int m_symmetryAxisStart;
int m_symmetryAxisNew;
gfx::Point m_mouseStart;
};
} // namespace app
#endif

View File

@ -27,6 +27,7 @@
#include "app/ui/editor/editor_customization_delegate.h"
#include "app/ui/editor/handle_type.h"
#include "app/ui/editor/moving_cel_state.h"
#include "app/ui/editor/moving_symmetry_state.h"
#include "app/ui/editor/moving_pixels_state.h"
#include "app/ui/editor/pivot_helpers.h"
#include "app/ui/editor/pixels_movement.h"
@ -34,6 +35,7 @@
#include "app/ui/editor/tool_loop_impl.h"
#include "app/ui/editor/transform_handles.h"
#include "app/ui/editor/zooming_state.h"
#include "app/ui/skin/skin_theme.h"
#include "app/ui/status_bar.h"
#include "app/ui_context.h"
#include "app/util/new_image_from_mask.h"
@ -44,6 +46,7 @@
#include "doc/sprite.h"
#include "fixmath/fixmath.h"
#include "gfx/rect.h"
#include "she/surface.h"
#include "ui/alert.h"
#include "ui/message.h"
#include "ui/system.h"
@ -225,7 +228,9 @@ bool StandbyState::onMouseDown(Editor* editor, MouseMessage* msg)
if (clickedInk->isSelection()) {
// Transform selected pixels
if (editor->isActive() && document->isMaskVisible() && m_decorator->getTransformHandles(editor)) {
if (editor->isActive() &&
document->isMaskVisible() &&
m_decorator->getTransformHandles(editor)) {
TransformHandles* transfHandles = m_decorator->getTransformHandles(editor);
// Get the handle covered by the mouse.
@ -264,6 +269,22 @@ 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;
}
// Start the Tool-Loop
if (layer) {
tools::ToolLoop* toolLoop = create_tool_loop(editor, context);
@ -305,13 +326,14 @@ bool StandbyState::onMouseMove(Editor* editor, MouseMessage* msg)
bool StandbyState::onSetCursor(Editor* editor, const gfx::Point& mouseScreenPos)
{
tools::Ink* ink = editor->getCurrentEditorInk();
// See if the cursor is in some selection handle.
if (m_decorator->onSetCursor(ink, editor, mouseScreenPos))
return true;
if (ink) {
// If the current tool change selection (e.g. rectangular marquee, etc.)
if (ink->isSelection()) {
// See if the cursor is in some selection handle.
if (m_decorator->onSetCursor(editor, mouseScreenPos))
return true;
// Move pixels
if (editor->isInsideSelection()) {
EditorCustomizationDelegate* customization = editor->getCustomizationDelegate();
@ -521,69 +543,87 @@ TransformHandles* StandbyState::Decorator::getTransformHandles(Editor* editor)
return m_transfHandles;
}
bool StandbyState::Decorator::onSetCursor(Editor* editor, const gfx::Point& mouseScreenPos)
bool StandbyState::Decorator::onSetCursor(tools::Ink* ink, Editor* editor, const gfx::Point& mouseScreenPos)
{
if (!editor->isActive() ||
!editor->document()->isMaskVisible())
if (!editor->isActive())
return false;
const gfx::Transformation transformation(m_standbyState->getTransformation(editor));
TransformHandles* tr = getTransformHandles(editor);
HandleType handle = tr->getHandleAtPoint(
editor, mouseScreenPos, transformation);
if (ink && ink->isSelection() && editor->document()->isMaskVisible()) {
const gfx::Transformation transformation(m_standbyState->getTransformation(editor));
TransformHandles* tr = getTransformHandles(editor);
HandleType handle = tr->getHandleAtPoint(
editor, mouseScreenPos, transformation);
CursorType newCursor = kArrowCursor;
CursorType newCursor = kArrowCursor;
switch (handle) {
case ScaleNWHandle: newCursor = kSizeNWCursor; break;
case ScaleNHandle: newCursor = kSizeNCursor; break;
case ScaleNEHandle: newCursor = kSizeNECursor; break;
case ScaleWHandle: newCursor = kSizeWCursor; break;
case ScaleEHandle: newCursor = kSizeECursor; break;
case ScaleSWHandle: newCursor = kSizeSWCursor; break;
case ScaleSHandle: newCursor = kSizeSCursor; break;
case ScaleSEHandle: newCursor = kSizeSECursor; break;
case RotateNWHandle: newCursor = kRotateNWCursor; break;
case RotateNHandle: newCursor = kRotateNCursor; break;
case RotateNEHandle: newCursor = kRotateNECursor; break;
case RotateWHandle: newCursor = kRotateWCursor; break;
case RotateEHandle: newCursor = kRotateECursor; break;
case RotateSWHandle: newCursor = kRotateSWCursor; break;
case RotateSHandle: newCursor = kRotateSCursor; break;
case RotateSEHandle: newCursor = kRotateSECursor; break;
case PivotHandle: newCursor = kHandCursor; break;
default:
return false;
switch (handle) {
case ScaleNWHandle: newCursor = kSizeNWCursor; break;
case ScaleNHandle: newCursor = kSizeNCursor; break;
case ScaleNEHandle: newCursor = kSizeNECursor; break;
case ScaleWHandle: newCursor = kSizeWCursor; break;
case ScaleEHandle: newCursor = kSizeECursor; break;
case ScaleSWHandle: newCursor = kSizeSWCursor; break;
case ScaleSHandle: newCursor = kSizeSCursor; break;
case ScaleSEHandle: newCursor = kSizeSECursor; break;
case RotateNWHandle: newCursor = kRotateNWCursor; break;
case RotateNHandle: newCursor = kRotateNCursor; break;
case RotateNEHandle: newCursor = kRotateNECursor; break;
case RotateWHandle: newCursor = kRotateWCursor; break;
case RotateEHandle: newCursor = kRotateECursor; break;
case RotateSWHandle: newCursor = kRotateSWCursor; break;
case RotateSHandle: newCursor = kRotateSCursor; break;
case RotateSEHandle: newCursor = kRotateSECursor; break;
case PivotHandle: newCursor = kHandCursor; break;
default:
return false;
}
// Adjust the cursor depending the current transformation angle.
fixmath::fixed angle = fixmath::ftofix(128.0 * transformation.angle() / PI);
angle = fixmath::fixadd(angle, fixmath::itofix(16));
angle &= (255<<16);
angle >>= 16;
angle /= 32;
if (newCursor >= kSizeNCursor && newCursor <= kSizeNWCursor) {
size_t num = sizeof(rotated_size_cursors) / sizeof(rotated_size_cursors[0]);
size_t c;
for (c=num-1; c>0; --c)
if (rotated_size_cursors[c] == newCursor)
break;
newCursor = rotated_size_cursors[(c+angle) % num];
}
else if (newCursor >= kRotateNCursor && newCursor <= kRotateNWCursor) {
size_t num = sizeof(rotated_rotate_cursors) / sizeof(rotated_rotate_cursors[0]);
size_t c;
for (c=num-1; c>0; --c)
if (rotated_rotate_cursors[c] == newCursor)
break;
newCursor = rotated_rotate_cursors[(c+angle) % num];
}
editor->showMouseCursor(newCursor);
return true;
}
// Adjust the cursor depending the current transformation angle.
fixmath::fixed angle = fixmath::ftofix(128.0 * transformation.angle() / PI);
angle = fixmath::fixadd(angle, fixmath::itofix(16));
angle &= (255<<16);
angle >>= 16;
angle /= 32;
if (newCursor >= kSizeNCursor && newCursor <= kSizeNWCursor) {
size_t num = sizeof(rotated_size_cursors) / sizeof(rotated_size_cursors[0]);
size_t c;
for (c=num-1; c>0; --c)
if (rotated_size_cursors[c] == newCursor)
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;
newCursor = rotated_size_cursors[(c+angle) % num];
}
else if (newCursor >= kRotateNCursor && newCursor <= kRotateNWCursor) {
size_t num = sizeof(rotated_rotate_cursors) / sizeof(rotated_rotate_cursors[0]);
size_t c;
for (c=num-1; c>0; --c)
if (rotated_rotate_cursors[c] == newCursor)
case app::gen::SymmetryMode::VERTICAL:
editor->showMouseCursor(kSizeNSCursor);
break;
newCursor = rotated_rotate_cursors[(c+angle) % num];
}
return true;
}
editor->showMouseCursor(newCursor);
return true;
return false;
}
void StandbyState::Decorator::preRenderDecorator(EditorPreRender* render)
@ -607,6 +647,59 @@ void StandbyState::Decorator::postRenderDecorator(EditorPostRender* render)
getTransformHandles(editor)->drawHandles(editor,
m_standbyState->getTransformation(editor));
}
// Draw transformation handles (if the mask is visible and isn't frozen).
gfx::Rect box1, box2;
if (StandbyState::Decorator::getSymmetryHandles(editor, box1, box2)) {
skin::SkinTheme* theme = static_cast<skin::SkinTheme*>(CurrentTheme::get());
she::Surface* part = theme->parts.transformationHandle()->getBitmap(0);
ScreenGraphics g;
g.drawRgbaSurface(part, box1.x, box1.y);
g.drawRgbaSurface(part, box2.x, box2.y);
}
}
bool StandbyState::Decorator::getSymmetryHandles(Editor* editor, gfx::Rect& box1, gfx::Rect& box2)
{
// Draw transformation handles (if the mask is visible and isn't frozen).
if (editor->isActive() &&
editor->editorFlags() & Editor::kShowSymmetryLine &&
Preferences::instance().symmetryMode.enabled()) {
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);
int pos = (horz ? symmetry.xAxis():
symmetry.yAxis());
gfx::Rect spriteBounds = editor->sprite()->bounds();
skin::SkinTheme* theme = static_cast<skin::SkinTheme*>(CurrentTheme::get());
she::Surface* part = theme->parts.transformationHandle()->getBitmap(0);
gfx::Point pt1, pt2;
if (horz) {
pt1 = gfx::Point(spriteBounds.x+pos, spriteBounds.y);
pt1 = editor->editorToScreen(pt1);
pt2 = gfx::Point(spriteBounds.x+pos, spriteBounds.y+spriteBounds.h);
pt2 = editor->editorToScreen(pt2);
pt1.y -= part->height();
pt1.x -= part->width()/2;
pt2.x -= part->width()/2;
}
else {
pt1 = gfx::Point(spriteBounds.x, spriteBounds.y+pos);
pt1 = editor->editorToScreen(pt1);
pt2 = gfx::Point(spriteBounds.x+spriteBounds.w, spriteBounds.y+pos);
pt2 = editor->editorToScreen(pt2);
pt1.x -= part->width();
pt1.y -= part->height()/2;
pt2.y -= part->height()/2;
}
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 false;
}
} // namespace app

View File

@ -16,6 +16,10 @@
#include "gfx/transformation.h"
namespace app {
namespace tools {
class Ink;
}
class TransformHandles;
class StandbyState : public StateWithWheelBehavior {
@ -54,12 +58,14 @@ namespace app {
virtual ~Decorator();
TransformHandles* getTransformHandles(Editor* editor);
bool getSymmetryHandles(Editor* editor, gfx::Rect& box1, gfx::Rect& box2);
bool onSetCursor(Editor* editor, const gfx::Point& mouseScreenPos);
bool onSetCursor(tools::Ink* ink, Editor* editor, const gfx::Point& mouseScreenPos);
// EditorDecorator overrides
void preRenderDecorator(EditorPreRender* render) override;
void postRenderDecorator(EditorPostRender* render) override;
private:
TransformHandles* m_transfHandles;
StandbyState* m_standbyState;

View File

@ -141,25 +141,21 @@ public:
}
// Symmetry mode
switch (m_docPref.symmetry.mode()) {
if (Preferences::instance().symmetryMode.enabled()) {
switch (m_docPref.symmetry.mode()) {
case app::gen::SymmetryMode::NONE:
ASSERT(m_symmetry == nullptr);
break;
case app::gen::SymmetryMode::NONE:
ASSERT(m_symmetry == nullptr);
break;
case app::gen::SymmetryMode::HORIZONTAL:
if (m_docPref.symmetry.xAxis() == 0)
m_docPref.symmetry.xAxis(m_sprite->width()/2);
case app::gen::SymmetryMode::HORIZONTAL:
m_symmetry.reset(new app::tools::HorizontalSymmetry(m_docPref.symmetry.xAxis()));
break;
m_symmetry.reset(new app::tools::HorizontalSymmetry(m_docPref.symmetry.xAxis()));
break;
case app::gen::SymmetryMode::VERTICAL:
if (m_docPref.symmetry.yAxis() == 0)
m_docPref.symmetry.yAxis(m_sprite->height()/2);
m_symmetry.reset(new app::tools::VerticalSymmetry(m_docPref.symmetry.yAxis()));
break;
case app::gen::SymmetryMode::VERTICAL:
m_symmetry.reset(new app::tools::VerticalSymmetry(m_docPref.symmetry.yAxis()));
break;
}
}
// Ignore opacity for these inks