diff --git a/data/skins/default/sheet.png b/data/skins/default/sheet.png index 4c3401f7c..f7efa1f53 100644 Binary files a/data/skins/default/sheet.png and b/data/skins/default/sheet.png differ diff --git a/data/skins/default/skin.xml b/data/skins/default/skin.xml index 76aaa5fc7..d917981dc 100644 --- a/data/skins/default/skin.xml +++ b/data/skins/default/skin.xml @@ -18,6 +18,14 @@ + + + + + + + + @@ -179,6 +187,8 @@ + + diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index f8ccd7a3a..2825333ed 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -320,6 +320,7 @@ add_library(aseprite-library widgets/editor/select_box_state.cpp widgets/editor/standby_state.cpp widgets/editor/tool_loop_impl.cpp + widgets/editor/transform_handles.cpp widgets/fileview.cpp widgets/groupbut.cpp widgets/hex_color_entry.cpp diff --git a/src/document.cpp b/src/document.cpp index bf008e74e..f6d458ca4 100644 --- a/src/document.cpp +++ b/src/document.cpp @@ -278,6 +278,8 @@ void Document::setMask(const Mask* mask) { m_mask.reset(mask_new_copy(mask)); m_maskVisible = true; + + resetTransformation(); } bool Document::isMaskVisible() const @@ -293,6 +295,27 @@ void Document::setMaskVisible(bool visible) m_maskVisible = visible; } +////////////////////////////////////////////////////////////////////// +// Transformation + +gfx::Transformation Document::getTransformation() const +{ + return m_transformation; +} + +void Document::setTransformation(const gfx::Transformation& transform) +{ + m_transformation = transform; +} + +void Document::resetTransformation() +{ + if (m_mask) + m_transformation = gfx::Transformation(m_mask->getBounds()); + else + m_transformation = gfx::Transformation(); +} + ////////////////////////////////////////////////////////////////////// // Copying diff --git a/src/document.h b/src/document.h index c5dc6eb41..311109c00 100644 --- a/src/document.h +++ b/src/document.h @@ -23,6 +23,7 @@ #include "base/shared_ptr.h" #include "base/unique_ptr.h" #include "document_id.h" +#include "gfx/transformation.h" #include @@ -145,6 +146,13 @@ public: // being hidden and shown to him). void setMaskVisible(bool visible); + ////////////////////////////////////////////////////////////////////// + // Transformation + + gfx::Transformation getTransformation() const; + void setTransformation(const gfx::Transformation& transform); + void resetTransformation(); + ////////////////////////////////////////////////////////////////////// // Copying @@ -222,6 +230,9 @@ private: UniquePtr m_mask; bool m_maskVisible; + // Current transformation. + gfx::Transformation m_transformation; + DISABLE_COPYING(Document); }; diff --git a/src/gfx/CMakeLists.txt b/src/gfx/CMakeLists.txt index 3475dbe18..d9f44adc2 100644 --- a/src/gfx/CMakeLists.txt +++ b/src/gfx/CMakeLists.txt @@ -3,4 +3,5 @@ add_library(gfx-lib hsv.cpp - rgb.cpp) + rgb.cpp + transformation.cpp) diff --git a/src/gfx/transformation.cpp b/src/gfx/transformation.cpp new file mode 100644 index 000000000..c152891c0 --- /dev/null +++ b/src/gfx/transformation.cpp @@ -0,0 +1,82 @@ +// ASE gfx library +// Copyright (C) 2001-2011 David Capello +// +// This source file is ditributed under a BSD-like license, please +// read LICENSE.txt for more information. + +#include "config.h" + +#include "gfx/transformation.h" +#include "gfx/point.h" +#include "gfx/size.h" + +#include + +namespace gfx { + +Transformation::Transformation() +{ + m_bounds.x = 0; + m_bounds.y = 0; + m_bounds.w = 0; + m_bounds.h = 0; + m_angle = 0.0; + m_pivot.x = 0; + m_pivot.y = 0; +} + +Transformation::Transformation(const Rect& bounds) + : m_bounds(bounds) +{ + m_angle = 0.0; + m_pivot.x = bounds.x + bounds.w/2; + m_pivot.y = bounds.y + bounds.h/2; +} + +void Transformation::transformBox(Corners& corners) const +{ + PointT pivot_f(m_pivot); + + // TODO We could create a composed 4x4 matrix with all + // transformation and apply the same matrix to avoid calling + // rotatePoint/cos/sin functions 4 times, anyway, it's not + // critical at this point. + + corners = m_bounds; + for (size_t c=0; c newBoundsOrigin = + rotatePoint(corners.leftTop(), + PointT(newPivot.x, newPivot.y), + -m_angle); + + // Change the new pivot. + m_pivot = newPivot; + m_bounds = Rect(Point(newBoundsOrigin), m_bounds.getSize()); +} + +PointT Transformation::rotatePoint( + const PointT& point, + const PointT& pivot, + double angle) +{ + double cos = std::cos(-angle); + double sin = std::sin(-angle); + double dx = (point.x-pivot.x); + double dy = (point.y-pivot.y); + return PointT( + pivot.x + dx*cos - dy*sin, + pivot.y + dy*cos + dx*sin); +} + +} diff --git a/src/gfx/transformation.h b/src/gfx/transformation.h new file mode 100644 index 000000000..c14869a60 --- /dev/null +++ b/src/gfx/transformation.h @@ -0,0 +1,97 @@ +// ASE gfx library +// Copyright (C) 2001-2011 David Capello +// +// This source file is ditributed under a BSD-like license, please +// read LICENSE.txt for more information. + +#ifndef TRANSFORMATION_H_INCLUDED +#define TRANSFORMATION_H_INCLUDED + +#include "gfx/point.h" +#include "gfx/rect.h" +#include + +namespace gfx { + +// Represents a transformation that can be done by the user in the +// document when he/she moves the mask using the selection handles. +class Transformation +{ +public: + class Corners { + public: + enum { + LEFT_TOP = 0, + RIGHT_TOP = 1, + RIGHT_BOTTOM = 2, + LEFT_BOTTOM = 3, + NUM_OF_CORNERS = 4 + }; + + Corners() : m_corners(NUM_OF_CORNERS) { } + + size_t size() const { return m_corners.size(); } + + PointT& operator[](int index) { return m_corners[index]; } + const PointT& operator[](int index) const { return m_corners[index]; } + + const PointT& leftTop() const { return m_corners[LEFT_TOP]; } + const PointT& rightTop() const { return m_corners[RIGHT_TOP]; } + const PointT& rightBottom() const { return m_corners[RIGHT_BOTTOM]; } + const PointT& leftBottom() const { return m_corners[LEFT_BOTTOM]; } + + void leftTop(const PointT& pt) { m_corners[LEFT_TOP] = pt; } + void rightTop(const PointT& pt) { m_corners[RIGHT_TOP] = pt; } + void rightBottom(const PointT& pt) { m_corners[RIGHT_BOTTOM] = pt; } + void leftBottom(const PointT& pt) { m_corners[LEFT_BOTTOM] = pt; } + + Corners& operator=(const gfx::Rect& bounds) { + m_corners[LEFT_TOP].x = bounds.x; + m_corners[LEFT_TOP].y = bounds.y; + m_corners[RIGHT_TOP].x = bounds.x + bounds.w; + m_corners[RIGHT_TOP].y = bounds.y; + m_corners[RIGHT_BOTTOM].x = bounds.x + bounds.w; + m_corners[RIGHT_BOTTOM].y = bounds.y + bounds.h; + m_corners[LEFT_BOTTOM].x = bounds.x; + m_corners[LEFT_BOTTOM].y = bounds.y + bounds.h; + return *this; + } + + private: + std::vector > m_corners; + }; + + Transformation(); + Transformation(const Rect& bounds); + + // Simple getters and setters. The angle is in radians. + + const Rect& bounds() const { return m_bounds; } + const Point& pivot() const { return m_pivot; } + double angle() const { return m_angle; } + + void bounds(const Rect& bounds) { m_bounds = bounds; } + void pivot(const Point& pivot) { m_pivot = pivot; } + void angle(double angle) { m_angle = angle; } + + // Applies the transformation (rotation with angle/pivot) to the + // current bounds (m_bounds). + void transformBox(Corners& corners) const; + + // Changes the pivot to another location, adjusting the bounds to + // keep the current rotated-corners in the same location. + void displacePivotTo(const Point& newPivot); + + // Static helper method to rotate points. + static PointT rotatePoint(const PointT& point, + const PointT& pivot, double angle); + +private: + Rect m_bounds; + Point m_pivot; + double m_angle; +}; + +} // namespace gfx + +#endif diff --git a/src/gui/system.h b/src/gui/system.h index 4ccf8d232..aaf5ed9c5 100644 --- a/src/gui/system.h +++ b/src/gui/system.h @@ -54,6 +54,14 @@ enum { JI_CURSOR_SIZE_BL, JI_CURSOR_SIZE_B, JI_CURSOR_SIZE_BR, + JI_CURSOR_ROTATE_TL, + JI_CURSOR_ROTATE_T, + JI_CURSOR_ROTATE_TR, + JI_CURSOR_ROTATE_L, + JI_CURSOR_ROTATE_R, + JI_CURSOR_ROTATE_BL, + JI_CURSOR_ROTATE_B, + JI_CURSOR_ROTATE_BR, JI_CURSOR_EYEDROPPER, JI_CURSORS }; diff --git a/src/la/LICENSE.txt b/src/la/LICENSE.txt new file mode 100644 index 000000000..0b1015aef --- /dev/null +++ b/src/la/LICENSE.txt @@ -0,0 +1,29 @@ +ASEPRITE Linear Algebra Library +Copyright (c) 2001-2011 David Capello +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the + distribution. + * Neither the name of the author nor the names of its contributors may + be used to endorse or promote products derived from this software + without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/src/la/vector2d.h b/src/la/vector2d.h new file mode 100644 index 000000000..ff9a676c6 --- /dev/null +++ b/src/la/vector2d.h @@ -0,0 +1,78 @@ +// ASEPRITE Linear Algebra Library +// Copyright (C) 2001-2011 David Capello +// +// This source file is ditributed under a BSD-like license, please +// read LICENSE.txt for more information. + +#ifndef LA_VECTOR2D_H_INCLUDED +#define LA_VECTOR2D_H_INCLUDED + +#include + +namespace la { + +template +class Vector2d { +public: + T x, y; + + Vector2d() { + x = y = 0.0; + } + + Vector2d(const T& u, const T& v) { + x = u; y = v; + } + + Vector2d(const Vector2d& v) { + x = v.x; y = v.y; + } + + Vector2d operator-() const { return Vector2d(-x, -y); } + + Vector2d operator+(const Vector2d& v) const { return Vector2d(x+v.x, y+v.y); } + Vector2d operator-(const Vector2d& v) const { return Vector2d(x-v.x, y-v.y); } + T operator*(const Vector2d& v) const { return dotProduct(v); } + Vector2d operator*(const T& f) const { return Vector2d(x*f, y*f); } + Vector2d operator/(const T& f) const { return Vector2d(x/f, y/f); } + + Vector2d& operator= (const Vector2d& v) { x=v.x; y=v.y; return *this; } + Vector2d& operator+=(const Vector2d& v) { x+=v.x; y+=v.y; return *this; } + Vector2d& operator-=(const Vector2d& v) { x-=v.x; y-=v.y; return *this; } + Vector2d& operator*=(const T& f) { x*=f; y*=f; return *this; } + Vector2d& operator/=(const T& f) { x/=f; y/=f; return *this; } + + T magnitude() const { + return std::sqrt(x*x + y*y); + } + + T dotProduct(const Vector2d& v) const { + return x*v.x + y*v.y; + } + + Vector2d projectOn(const Vector2d& v) const { + return v * (this->dotProduct(v) / std::pow(v.magnitude(), 2)); + } + + T angle() const { + return std::atan2(y, x); + } + + Vector2d normalize() const { + return Vector2d(*this) / magnitude(); + } + + Vector2d getNormal() const { + return Vector2d(y, -x); + } + +}; + +} // namespace la + +template +la::Vector2d operator*(const T& f, const la::Vector2d& v) { + return la::Vector2d(v.x*f, v.y*f); +} + +#endif // LA_VECTOR2D_H_INCLUDED diff --git a/src/raster/mask.cpp b/src/raster/mask.cpp index 7423cc286..efdf79c02 100644 --- a/src/raster/mask.cpp +++ b/src/raster/mask.cpp @@ -83,6 +83,11 @@ void Mask::unfreeze() shrink(); } +gfx::Rect Mask::getBounds() const +{ + return gfx::Rect(x, y, w, h); +} + ////////////////////////////////////////////////////////////////////// Mask* mask_new() diff --git a/src/raster/mask.h b/src/raster/mask.h index 711765618..d2ca60396 100644 --- a/src/raster/mask.h +++ b/src/raster/mask.h @@ -19,18 +19,18 @@ #ifndef RASTER_MASK_H_INCLUDED #define RASTER_MASK_H_INCLUDED +#include "gfx/rect.h" #include "raster/gfxobj.h" #include "raster/image.h" // Represents the selection (selected pixels, 0/1, 0=non-selected, 1=selected) class Mask : public GfxObj { - int m_freeze_count; public: // TODO private this - char* name; /* mask name */ - int x, y, w, h; /* region bounds */ - Image* bitmap; /* bitmapped image mask */ + char* name; // Mask name + int x, y, w, h; // Region bounds + Image* bitmap; // Bitmapped image mask Mask(); Mask(const Mask& mask); @@ -52,12 +52,17 @@ public: image_getpixel(bitmap, u-x, v-y)); } + gfx::Rect getBounds() const; + // These functions can be used to disable the automatic call to // "shrink" method (so you can do a lot of modifications without - // lossing time shrink the mask in each little operation) + // lossing time shrinking the mask in each little operation). void freeze(); void unfreeze(); + // Returns true if the mask is frozen (See freeze/unfreeze functions). + bool isFrozen() const { return m_freeze_count > 0; } + // Adds the specified rectangle in the mask/selection void add(int x, int y, int w, int h); @@ -71,6 +76,8 @@ public: private: void initialize(); + + int m_freeze_count; }; Mask* mask_new(); diff --git a/src/raster/rotate.cpp b/src/raster/rotate.cpp index 9e1e0dfe9..9509db6ee 100644 --- a/src/raster/rotate.cpp +++ b/src/raster/rotate.cpp @@ -132,50 +132,78 @@ static void draw_scanline(Image *bmp, Image *spr, fixed l_spr_x, fixed l_spr_y, fixed spr_dx, fixed spr_dy) { - Traits::address_t addr, end_addr; - Traits::address_t* spr_line = (Traits::address_t*)spr->line; Delegate delegate; r_bmp_x >>= 16; l_bmp_x >>= 16; - addr = ((Traits::address_t*)bmp->line)[bmp_y_i]; - end_addr = addr + r_bmp_x; - addr += l_bmp_x; - for (; addr <= end_addr; ++addr) { - delegate.putpixel(addr, spr_line, l_spr_x, l_spr_y); + delegate.startScan(bmp, l_bmp_x, bmp_y_i); + + for (int x=(int)l_bmp_x; x<=(int)r_bmp_x; ++x) { + delegate.feedLine(spr, l_spr_x, l_spr_y); + l_spr_x += spr_dx; l_spr_y += spr_dy; } } -class RgbDelegate { +template +class GenericDelegate { +protected: + typename Traits::address_t m_addr; +public: + void startScan(Image* bmp, int x, int y) { + m_addr = ((Traits::address_t*)bmp->line)[y]+x; + } +}; + +class RgbDelegate : public GenericDelegate { BLEND_COLOR m_blender; public: RgbDelegate() : m_blender(_rgba_blenders[BLEND_MODE_NORMAL]) { } - void putpixel(RgbTraits::address_t addr, RgbTraits::address_t* spr_line, fixed l_spr_x, fixed l_spr_y) { - *addr = m_blender(*addr, spr_line[l_spr_y>>16][l_spr_x>>16], 255); + void feedLine(Image* spr, fixed l_spr_x, fixed l_spr_y) { + *m_addr = m_blender(*m_addr, image_getpixel_fast(spr, l_spr_x>>16, l_spr_y>>16), 255); + ++m_addr; } }; -class GrayscaleDelegate { +class GrayscaleDelegate : public GenericDelegate { BLEND_COLOR m_blender; public: GrayscaleDelegate() : m_blender(_graya_blenders[BLEND_MODE_NORMAL]) { } - void putpixel(GrayscaleTraits::address_t addr, GrayscaleTraits::address_t* spr_line, fixed l_spr_x, fixed l_spr_y) { - *addr = m_blender(*addr, spr_line[l_spr_y>>16][l_spr_x>>16], 255); + void feedLine(Image* spr, fixed l_spr_x, fixed l_spr_y) { + *m_addr = m_blender(*m_addr, image_getpixel_fast(spr, l_spr_x>>16, l_spr_y>>16), 255); + ++m_addr; } }; -class IndexedDelegate { +class IndexedDelegate : public GenericDelegate { public: - IndexedDelegate() { } + void feedLine(Image* spr, fixed l_spr_x, fixed l_spr_y) { + register int c = image_getpixel_fast(spr, l_spr_x>>16, l_spr_y>>16); + if (c != 0) // TODO + *m_addr = c; + ++m_addr; + } +}; - void putpixel(IndexedTraits::address_t addr, IndexedTraits::address_t* spr_line, fixed l_spr_x, fixed l_spr_y) { - register int c = spr_line[l_spr_y>>16][l_spr_x>>16]; - if (c != 0) *addr = c; +class BitmapDelegate : public GenericDelegate { + div_t m_d; +public: + void startScan(Image* bmp, int x, int y) { + m_d = div(x, 8); + m_addr = ((BitmapTraits::address_t*)bmp->line)[y] + m_d.quot; + } + + void feedLine(Image* spr, fixed l_spr_x, fixed l_spr_y) { + if (image_getpixel_fast(spr, l_spr_x>>16, l_spr_y>>16)) + *m_addr |= (1<(bmp, sprite, xs, ys, false); break; + + case IMAGE_BITMAP: + ase_parallelogram_map(bmp, sprite, xs, ys, false); + break; } } diff --git a/src/skin/skin_parts.h b/src/skin/skin_parts.h index 3954d528f..ec891f151 100644 --- a/src/skin/skin_parts.h +++ b/src/skin/skin_parts.h @@ -540,6 +540,9 @@ enum { PART_DROP_DOWN_BUTTON_RIGHT_SELECTED_SW, PART_DROP_DOWN_BUTTON_RIGHT_SELECTED_W, + PART_TRANSFORMATION_HANDLE, + PART_PIVOT_HANDLE, + PARTS }; diff --git a/src/skin/skin_theme.cpp b/src/skin/skin_theme.cpp index 29b231f9f..2b17c918e 100644 --- a/src/skin/skin_theme.cpp +++ b/src/skin/skin_theme.cpp @@ -73,6 +73,14 @@ static struct { "size_bl", 0, 0 }, // JI_CURSOR_SIZE_BL { "size_b", 0, 0 }, // JI_CURSOR_SIZE_B { "size_br", 0, 0 }, // JI_CURSOR_SIZE_BR + { "rotate_tl", 0, 0 }, // JI_CURSOR_ROTATE_TL + { "rotate_t", 0, 0 }, // JI_CURSOR_ROTATE_T + { "rotate_tr", 0, 0 }, // JI_CURSOR_ROTATE_TR + { "rotate_l", 0, 0 }, // JI_CURSOR_ROTATE_L + { "rotate_r", 0, 0 }, // JI_CURSOR_ROTATE_R + { "rotate_bl", 0, 0 }, // JI_CURSOR_ROTATE_BL + { "rotate_b", 0, 0 }, // JI_CURSOR_ROTATE_B + { "rotate_br", 0, 0 }, // JI_CURSOR_ROTATE_BR { "eyedropper", 0, 0 }, // JI_CURSOR_EYEDROPPER }; @@ -209,6 +217,8 @@ SkinTheme::SkinTheme() sheet_mapping["drop_down_button_right_hot"] = PART_DROP_DOWN_BUTTON_RIGHT_HOT_NW; sheet_mapping["drop_down_button_right_focused"] = PART_DROP_DOWN_BUTTON_RIGHT_FOCUSED_NW; sheet_mapping["drop_down_button_right_selected"] = PART_DROP_DOWN_BUTTON_RIGHT_SELECTED_NW; + sheet_mapping["transformation_handle"] = PART_TRANSFORMATION_HANDLE; + sheet_mapping["pivot_handle"] = PART_PIVOT_HANDLE; reload_skin(); } diff --git a/src/tools/inks.h b/src/tools/inks.h index f84cdaa62..f789c846f 100644 --- a/src/tools/inks.h +++ b/src/tools/inks.h @@ -22,6 +22,7 @@ #include "app/color_utils.h" #include "context.h" #include "document.h" +#include "raster/mask.h" #include "undo/undo_history.h" #include "undoers/set_mask.h" @@ -269,6 +270,7 @@ public: } else { loop->getMask()->unfreeze(); + loop->getDocument()->setTransformation(Transformation(loop->getMask()->getBounds())); loop->getDocument()->setMaskVisible(true); } } diff --git a/src/widgets/editor/handle_type.h b/src/widgets/editor/handle_type.h new file mode 100644 index 000000000..3bab15aa7 --- /dev/null +++ b/src/widgets/editor/handle_type.h @@ -0,0 +1,41 @@ +/* ASE - Allegro Sprite Editor + * Copyright (C) 2001-2011 David Capello + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef WIDGETS_EDITOR_HANDLE_TYPE_H_INCLUDED +#define WIDGETS_EDITOR_HANDLE_TYPE_H_INCLUDED + +// Handles available to transform a region of pixels in the editor. +enum HandleType { + // No handle selected + NoHandle, + // This is the handle to move the pixels region, generally, the + // whole region activates this handle. + //MoveHandle, + // One of the region's corders to scale. + ScaleNWHandle, ScaleNHandle, ScaleNEHandle, + ScaleWHandle, ScaleEHandle, + ScaleSWHandle, ScaleSHandle, ScaleSEHandle, + // One of the region's corders to rotate. + RotateNWHandle, RotateNHandle, RotateNEHandle, + RotateWHandle, RotateEHandle, + RotateSWHandle, RotateSHandle, RotateSEHandle, + // Handle used to move the pivot + PivotHandle, +}; + +#endif diff --git a/src/widgets/editor/moving_pixels_state.cpp b/src/widgets/editor/moving_pixels_state.cpp index ea8febba4..1cee3aaba 100644 --- a/src/widgets/editor/moving_pixels_state.cpp +++ b/src/widgets/editor/moving_pixels_state.cpp @@ -37,11 +37,12 @@ #include "widgets/editor/editor_customization_delegate.h" #include "widgets/editor/pixels_movement.h" #include "widgets/editor/standby_state.h" +#include "widgets/editor/transform_handles.h" #include "widgets/statebar.h" #include -MovingPixelsState::MovingPixelsState(Editor* editor, Message* msg, Image* imge, int x, int y, int opacity) +MovingPixelsState::MovingPixelsState(Editor* editor, Message* msg, Image* imge, int x, int y, int opacity, HandleType handle) { EditorCustomizationDelegate* customization = editor->getCustomizationDelegate(); @@ -62,7 +63,7 @@ MovingPixelsState::MovingPixelsState(Editor* editor, Message* msg, Image* imge, m_pixelsMovement->cutMask(); editor->screenToEditor(msg->mouse.x, msg->mouse.y, &x, &y); - m_pixelsMovement->catchImage(x, y); + m_pixelsMovement->catchImage(x, y, handle); // Setup mask color setTransparentColor(app_get_statusbar()->getTransparentColor()); @@ -90,6 +91,9 @@ bool MovingPixelsState::onBeforeChangeState(Editor* editor) // Drop pixels if the user press a button outside the selection m_pixelsMovement->dropImage(); + + editor->getDocument()->resetTransformation(); + delete m_pixelsMovement; m_pixelsMovement = NULL; @@ -119,13 +123,37 @@ bool MovingPixelsState::onMouseDown(Editor* editor, Message* msg) { ASSERT(m_pixelsMovement != NULL); + Decorator* decorator = static_cast(editor->getDecorator()); + Document* document = editor->getDocument(); + + // Transform selected pixels + if (document->isMaskVisible() && + decorator->getTransformHandles(editor)) { + TransformHandles* transfHandles = decorator->getTransformHandles(editor); + + // Get the handle covered by the mouse. + HandleType handle = transfHandles->getHandleAtPoint(editor, + gfx::Point(msg->mouse.x, msg->mouse.y), + getTransformation(editor)); + + if (handle != NoHandle) { + // Re-catch the image + int x, y; + editor->screenToEditor(msg->mouse.x, msg->mouse.y, &x, &y); + m_pixelsMovement->catchImageAgain(x, y, handle); + + editor->captureMouse(); + return true; + } + } + // Start "moving pixels" loop if (editor->isInsideSelection() && (msg->mouse.left || msg->mouse.right)) { // Re-catch the image int x, y; editor->screenToEditor(msg->mouse.x, msg->mouse.y, &x, &y); - m_pixelsMovement->catchImageAgain(x, y); + m_pixelsMovement->catchImageAgain(x, y, NoHandle); editor->captureMouse(); return true; @@ -147,6 +175,9 @@ bool MovingPixelsState::onMouseUp(Editor* editor, Message* msg) // Drop the image temporarily in this location (where the user releases the mouse) m_pixelsMovement->dropImageTemporarily(); + // Redraw the new pivot location. + invalidate(); + editor->releaseMouse(); return true; } @@ -198,7 +229,7 @@ bool MovingPixelsState::onSetCursor(Editor* editor) ASSERT(m_pixelsMovement != NULL); // Move selection - if (m_pixelsMovement->isDragging() || editor->isInsideSelection()) { + if (m_pixelsMovement->isDragging()) { editor->hideDrawingCursor(); jmouse_set_cursor(JI_CURSOR_MOVE); return true; @@ -214,7 +245,7 @@ bool MovingPixelsState::onKeyDown(Editor* editor, Message* msg) EditorCustomizationDelegate* customization = editor->getCustomizationDelegate(); if (customization && customization->isCopySelectionKeyPressed()) { - // If the user press the CTRL key when he is dragging pixels (but + // If the user presses the CTRL key when he is dragging pixels (but // not pressing the mouse buttons). if (!jmouse_b(0) && m_pixelsMovement) { // Drop pixels (sure the user will press the mouse button to @@ -239,11 +270,13 @@ bool MovingPixelsState::onUpdateStatusBar(Editor* editor) { ASSERT(m_pixelsMovement != NULL); - gfx::Rect bounds = m_pixelsMovement->getImageBounds(); + const gfx::Transformation& transform(getTransformation(editor)); app_get_statusbar()->setStatusText - (100, "Pos %d %d, Size %d %d", - bounds.x, bounds.y, bounds.w, bounds.h); + (100, "Pos %d %d, Size %d %d, Angle %.1f", + transform.bounds().x, transform.bounds().y, + transform.bounds().w, transform.bounds().h, + 180.0 * transform.angle() / PI); return true; } @@ -277,3 +310,8 @@ void MovingPixelsState::dropPixels(Editor* editor) // receive an onBeforeChangeState event after this call. editor->backToPreviousState(); } + +gfx::Transformation MovingPixelsState::getTransformation(Editor* editor) +{ + return m_pixelsMovement->getTransformation(); +} diff --git a/src/widgets/editor/moving_pixels_state.h b/src/widgets/editor/moving_pixels_state.h index affa404f3..129ccc2a5 100644 --- a/src/widgets/editor/moving_pixels_state.h +++ b/src/widgets/editor/moving_pixels_state.h @@ -20,6 +20,7 @@ #define WIDGETS_EDITOR_MOVING_PIXELS_STATE_H_INCLUDED #include "base/compiler_specific.h" +#include "widgets/editor/handle_type.h" #include "widgets/editor/standby_state.h" #include "widgets/statebar.h" @@ -30,7 +31,8 @@ class PixelsMovement; class MovingPixelsState : public StandbyState, StatusBarListener { public: - MovingPixelsState(Editor* editor, Message* msg, Image* imge, int x, int y, int opacity); + MovingPixelsState(Editor* editor, Message* msg, Image* imge, int x, int y, int opacity, + HandleType handle); virtual ~MovingPixelsState(); virtual bool onBeforeChangeState(Editor* editor) OVERRIDE; @@ -44,6 +46,8 @@ public: virtual bool onKeyUp(Editor* editor, Message* msg) OVERRIDE; virtual bool onUpdateStatusBar(Editor* editor) OVERRIDE; + virtual gfx::Transformation getTransformation(Editor* editor) OVERRIDE; + protected: // StatusBarListener interface virtual void dispose() OVERRIDE; @@ -53,7 +57,7 @@ private: void setTransparentColor(const Color& color); void dropPixels(Editor* editor); - // Helper member to move selection. + // Helper member to move/translate selection and pixels. PixelsMovement* m_pixelsMovement; }; diff --git a/src/widgets/editor/pixels_movement.cpp b/src/widgets/editor/pixels_movement.cpp index f55321df8..592e25005 100644 --- a/src/widgets/editor/pixels_movement.cpp +++ b/src/widgets/editor/pixels_movement.cpp @@ -22,33 +22,49 @@ #include "app.h" #include "document.h" +#include "la/vector2d.h" #include "modules/gui.h" #include "raster/cel.h" #include "raster/image.h" #include "raster/mask.h" +#include "raster/rotate.h" #include "raster/sprite.h" #include "util/expand_cel_canvas.h" -using namespace gfx; +template +static inline const la::Vector2d point2Vector(const gfx::PointT& pt) { + return la::Vector2d(pt.x, pt.y); +} -PixelsMovement::PixelsMovement(Document* document, Sprite* sprite, const Image* moveThis, int initial_x, int initial_y, int opacity) +PixelsMovement::PixelsMovement(Document* document, Sprite* sprite, const Image* moveThis, int initialX, int initialY, int opacity) : m_documentReader(document) , m_sprite(sprite) , m_undoTransaction(document, "Pixels Movement") - , m_initial_x(initial_x) - , m_initial_y(initial_y) , m_firstDrop(true) , m_isDragging(false) + , m_adjustPivot(false) + , m_handle(NoHandle) + , m_originalImage(image_new_copy(moveThis)) { + m_initialData = gfx::Transformation(gfx::Rect(initialX, initialY, moveThis->w, moveThis->h)); + m_currentData = m_initialData; + DocumentWriter documentWriter(m_documentReader); - documentWriter->prepareExtraCel(initial_x, initial_y, moveThis->w, moveThis->h, opacity); + documentWriter->prepareExtraCel(0, 0, m_sprite->getWidth(), m_sprite->getHeight(), opacity); Image* extraImage = documentWriter->getExtraCelImage(); - image_copy(extraImage, moveThis, 0, 0); + image_clear(extraImage, extraImage->mask_color); + image_copy(extraImage, moveThis, initialX, initialY); + + m_initialMask = new Mask(*documentWriter->getMask()); + m_currentMask = new Mask(*documentWriter->getMask()); } PixelsMovement::~PixelsMovement() { + delete m_originalImage; + delete m_initialMask; + delete m_currentMask; } void PixelsMovement::cutMask() @@ -73,25 +89,27 @@ void PixelsMovement::copyMask() update_screen_for_document(m_documentReader); } -void PixelsMovement::catchImage(int x, int y) +void PixelsMovement::catchImage(int x, int y, HandleType handle) { - m_catch_x = x; - m_catch_y = y; + m_catchX = x; + m_catchY = y; m_isDragging = true; + m_handle = handle; } -void PixelsMovement::catchImageAgain(int x, int y) +void PixelsMovement::catchImageAgain(int x, int y, HandleType handle) { // Create a new UndoTransaction to move the pixels to other position - const Cel* cel = m_documentReader->getExtraCel(); - m_initial_x = cel->getX(); - m_initial_y = cel->getY(); + m_initialData = m_currentData; m_isDragging = true; - m_catch_x = x; - m_catch_y = y; + m_catchX = x; + m_catchY = y; - // Hide the mask (do not deselect it, it will be moved them using m_undoTransaction.setMaskPosition) + m_handle = handle; + + // Hide the mask (do not deselect it, it will be moved them using + // m_undoTransaction.setMaskPosition) Mask emptyMask; { DocumentWriter documentWriter(m_documentReader); @@ -101,57 +119,188 @@ void PixelsMovement::catchImageAgain(int x, int y) update_screen_for_document(m_documentReader); } -Rect PixelsMovement::moveImage(int x, int y) +gfx::Rect PixelsMovement::moveImage(int x, int y) { DocumentWriter documentWriter(m_documentReader); - Cel* cel = documentWriter->getExtraCel(); Image* image = documentWriter->getExtraCelImage(); + Cel* cel = documentWriter->getExtraCel(); int x1, y1, x2, y2; - int u1, v1, u2, v2; - x1 = cel->getX(); - y1 = cel->getY(); - x2 = cel->getX() + image->w; - y2 = cel->getY() + image->h; + x1 = m_initialData.bounds().x; + y1 = m_initialData.bounds().y; + x2 = m_initialData.bounds().x + m_initialData.bounds().w; + y2 = m_initialData.bounds().y + m_initialData.bounds().h; - int new_x = m_initial_x + x - m_catch_x; - int new_y = m_initial_y + y - m_catch_y; + bool updateBounds = false; + int dx, dy; - // No movement - if (cel->getX() == new_x && cel->getY() == new_y) - return Rect(); + dx = ((x - m_catchX) * cos(m_currentData.angle()) + + (y - m_catchY) * -sin(m_currentData.angle())); + dy = ((x - m_catchX) * sin(m_currentData.angle()) + + (y - m_catchY) * cos(m_currentData.angle())); - cel->setPosition(new_x, new_y); + switch (m_handle) { - u1 = cel->getX(); - v1 = cel->getY(); - u2 = cel->getX() + image->w; - v2 = cel->getY() + image->h; + case NoHandle: + x1 += dx; + y1 += dy; + x2 += dx; + y2 += dy; + updateBounds = true; + break; - return Rect(MIN(x1, u1), MIN(y1, v1), - MAX(x2, u2) - MIN(x1, u1) + 1, - MAX(y2, v2) - MIN(y1, v1) + 1); + case ScaleNWHandle: + x1 = MIN(x1+dx, x2-1); + y1 = MIN(y1+dy, y2-1); + updateBounds = true; + break; + + case ScaleNHandle: + y1 = MIN(y1+dy, y2-1); + updateBounds = true; + break; + + case ScaleNEHandle: + x2 = MAX(x2+dx, x1+1); + y1 = MIN(y1+dy, y2-1); + updateBounds = true; + break; + + case ScaleWHandle: + x1 = MIN(x1+dx, x2-1); + updateBounds = true; + break; + + case ScaleEHandle: + x2 = MAX(x2+dx, x1+1); + updateBounds = true; + break; + + case ScaleSWHandle: + x1 = MIN(x1+dx, x2-1); + y2 = MAX(y2+dy, y1+1); + updateBounds = true; + break; + + case ScaleSHandle: + y2 = MAX(y2+dy, y1+1); + updateBounds = true; + break; + + case ScaleSEHandle: + x2 = MAX(x2+dx, x1+1); + y2 = MAX(y2+dy, y1+1); + updateBounds = true; + break; + + case RotateNWHandle: + case RotateNHandle: + case RotateNEHandle: + case RotateWHandle: + case RotateEHandle: + case RotateSWHandle: + case RotateSHandle: + case RotateSEHandle: + { + gfx::Point abs_initial_pivot = m_initialData.pivot(); + gfx::Point abs_pivot = m_currentData.pivot(); + + double newAngle = + m_initialData.angle() + + atan2((double)(-y + abs_pivot.y), + (double)(+x - abs_pivot.x)) + - atan2((double)(-m_catchY + abs_initial_pivot.y), + (double)(+m_catchX - abs_initial_pivot.x)); + + m_currentData.angle(newAngle); + } + break; + + case PivotHandle: + { + // Calculate the new position of the pivot + gfx::Point newPivot(m_initialData.pivot().x + (x - m_catchX), + m_initialData.pivot().y + (y - m_catchY)); + + m_currentData = m_initialData; + m_currentData.displacePivotTo(newPivot); + } + break; + } + + if (updateBounds) { + m_currentData.bounds(gfx::Rect(x1, y1, x2 - x1, y2 - y1)); + m_adjustPivot = true; + } + + gfx::Transformation::Corners corners; + m_currentData.transformBox(corners); + + // Transform the extra-cel which is the chunk of pixels that the user is moving. + image_clear(documentWriter->getExtraCelImage(), 0); + image_parallelogram(documentWriter->getExtraCelImage(), m_originalImage, + corners.leftTop().x, corners.leftTop().y, + corners.rightTop().x, corners.rightTop().y, + corners.rightBottom().x, corners.rightBottom().y, + corners.leftBottom().x, corners.leftBottom().y); + + // Transform mask + mask_replace(m_currentMask, 0, 0, m_sprite->getWidth(), m_sprite->getHeight()); + m_currentMask->freeze(); + image_clear(m_currentMask->bitmap, 0); + image_parallelogram(m_currentMask->bitmap, m_initialMask->bitmap, + corners.leftTop().x, corners.leftTop().y, + corners.rightTop().x, corners.rightTop().y, + corners.rightBottom().x, corners.rightBottom().y, + corners.leftBottom().x, corners.leftBottom().y); + m_currentMask->unfreeze(); + + if (m_firstDrop) + m_undoTransaction.copyToCurrentMask(m_currentMask); + else + documentWriter->setMask(m_currentMask); + + documentWriter->setTransformation(m_currentData); + + return gfx::Rect(0, 0, m_sprite->getWidth(), m_sprite->getHeight()); } void PixelsMovement::dropImageTemporarily() { m_isDragging = false; - const Cel* cel = m_documentReader->getExtraCel(); - { DocumentWriter documentWriter(m_documentReader); - // Show the mask again in the new position - if (m_firstDrop) { - m_firstDrop = false; - m_undoTransaction.setMaskPosition(cel->getX(), cel->getY()); + // TODO Add undo information so the user can undo each transformation step. + + // Displace the pivot to the new location: + if (m_adjustPivot) { + m_adjustPivot = false; + + // Get the a factor for the X/Y position of the initial pivot + // position inside the initial non-rotated bounds. + gfx::PointT pivotPosFactor(m_initialData.pivot() - m_initialData.bounds().getOrigin()); + pivotPosFactor.x /= m_initialData.bounds().w; + pivotPosFactor.y /= m_initialData.bounds().h; + + // Get the current transformed bounds. + gfx::Transformation::Corners corners; + m_currentData.transformBox(corners); + + // The new pivot will be located from the rotated left-top + // corner a distance equal to the transformed bounds's + // width/height multiplied with the previously calculated X/Y + // factor. + la::Vector2d newPivot(corners.leftTop().x, + corners.leftTop().y); + newPivot += pivotPosFactor.x * point2Vector(corners.rightTop() - corners.leftTop()); + newPivot += pivotPosFactor.y * point2Vector(corners.leftBottom() - corners.leftTop()); + + m_currentData.displacePivotTo(gfx::Point(newPivot.x, newPivot.y)); } - else { - documentWriter->getMask()->x = cel->getX(); - documentWriter->getMask()->y = cel->getY(); - } - documentWriter->generateMaskBoundaries(); + + documentWriter->generateMaskBoundaries(m_currentMask); } update_screen_for_document(m_documentReader); @@ -173,8 +322,8 @@ void PixelsMovement::dropImage() m_sprite->getCurrentLayer(), TILED_NONE); image_merge(expandCelCanvas.getDestCanvas(), image, - cel->getX()-expandCelCanvas.getCel()->getX(), - cel->getY()-expandCelCanvas.getCel()->getY(), + -expandCelCanvas.getCel()->getX(), + -expandCelCanvas.getCel()->getY(), cel->getOpacity(), BLEND_MODE_NORMAL); expandCelCanvas.commit(); @@ -190,7 +339,7 @@ bool PixelsMovement::isDragging() return m_isDragging; } -Rect PixelsMovement::getImageBounds() +gfx::Rect PixelsMovement::getImageBounds() { const Cel* cel = m_documentReader->getExtraCel(); const Image* image = m_documentReader->getExtraCelImage(); @@ -198,7 +347,7 @@ Rect PixelsMovement::getImageBounds() ASSERT(cel != NULL); ASSERT(image != NULL); - return Rect(cel->getX(), cel->getY(), image->w, image->h); + return gfx::Rect(cel->getX(), cel->getY(), image->w, image->h); } void PixelsMovement::setMaskColor(uint32_t mask_color) diff --git a/src/widgets/editor/pixels_movement.h b/src/widgets/editor/pixels_movement.h index 3a069a881..8830aba06 100644 --- a/src/widgets/editor/pixels_movement.h +++ b/src/widgets/editor/pixels_movement.h @@ -21,22 +21,29 @@ #include "document_wrappers.h" #include "undo_transaction.h" +#include "widgets/editor/handle_type.h" class Document; class Image; class Sprite; -// Uses the extra cel of the sprite to move/paste the specified image. +// Helper class to move pixels interactively and control undo history +// correctly. The extra cel of the sprite is temporally used to show +// feedback, drag, and drop the specified image in the constructor +// (which generally would be the selected region or the clipboard +// content). class PixelsMovement { public: + // The "moveThis" image specifies the chunk of pixels to be moved. + // The "x" and "y" parameters specify the initial position of the image. PixelsMovement(Document* document, Sprite* sprite, const Image* moveThis, int x, int y, int opacity); ~PixelsMovement(); void cutMask(); void copyMask(); - void catchImage(int x, int y); - void catchImageAgain(int x, int y); + void catchImage(int x, int y, HandleType handle); + void catchImageAgain(int x, int y, HandleType handle); // Moves the image to the new position (relative to the start // position given in the ctor). Returns the rectangle that should be @@ -51,14 +58,22 @@ public: void setMaskColor(uint32_t mask_color); + const gfx::Transformation& getTransformation() const { return m_currentData; } + private: const DocumentReader m_documentReader; Sprite* m_sprite; UndoTransaction m_undoTransaction; - int m_initial_x, m_initial_y; - int m_catch_x, m_catch_y; bool m_firstDrop; bool m_isDragging; + bool m_adjustPivot; + HandleType m_handle; + Image* m_originalImage; + int m_catchX, m_catchY; + gfx::Transformation m_initialData; + gfx::Transformation m_currentData; + Mask* m_initialMask; + Mask* m_currentMask; }; #endif diff --git a/src/widgets/editor/standby_state.cpp b/src/widgets/editor/standby_state.cpp index 387ccc4d9..408c99659 100644 --- a/src/widgets/editor/standby_state.cpp +++ b/src/widgets/editor/standby_state.cpp @@ -40,10 +40,12 @@ #include "widgets/editor/drawing_state.h" #include "widgets/editor/editor.h" #include "widgets/editor/editor_customization_delegate.h" +#include "widgets/editor/handle_type.h" #include "widgets/editor/moving_cel_state.h" #include "widgets/editor/moving_pixels_state.h" #include "widgets/editor/scrolling_state.h" #include "widgets/editor/tool_loop_impl.h" +#include "widgets/editor/transform_handles.h" #include "widgets/statebar.h" #include @@ -56,17 +58,57 @@ enum WHEEL_ACTION { WHEEL_NONE, WHEEL_BG, WHEEL_FRAME }; +static int rotated_size_cursors[] = { + JI_CURSOR_SIZE_R, + JI_CURSOR_SIZE_TR, + JI_CURSOR_SIZE_T, + JI_CURSOR_SIZE_TL, + JI_CURSOR_SIZE_L, + JI_CURSOR_SIZE_BL, + JI_CURSOR_SIZE_B, + JI_CURSOR_SIZE_BR +}; + +static int rotated_rotate_cursors[] = { + JI_CURSOR_ROTATE_R, + JI_CURSOR_ROTATE_TR, + JI_CURSOR_ROTATE_T, + JI_CURSOR_ROTATE_TL, + JI_CURSOR_ROTATE_L, + JI_CURSOR_ROTATE_BL, + JI_CURSOR_ROTATE_B, + JI_CURSOR_ROTATE_BR +}; + static inline bool has_shifts(Message* msg, int shift) { return ((msg->any.shifts & shift) == shift); } StandbyState::StandbyState() + : m_decorator(new Decorator(this)) { } StandbyState::~StandbyState() { + delete m_decorator; +} + +void StandbyState::onAfterChangeState(Editor* editor) +{ + editor->setDecorator(m_decorator); +} + +void StandbyState::onCurrentToolChange(Editor* editor) +{ + tools::Tool* currentTool = editor->getCurrentEditorTool(); + + // If the user change from a selection tool to a non-selection tool, + // or viceversa, we've to show or hide the transformation handles. + // TODO Compare the ink (isSelection()) of the previous tool with + // the new one. + editor->invalidate(); } bool StandbyState::onMouseDown(Editor* editor, Message* msg) @@ -82,7 +124,9 @@ bool StandbyState::onMouseDown(Editor* editor, Message* msg) // Each time an editor is clicked the current editor and the active // document are set. set_current_editor(editor); - context->setActiveDocument(editor->getDocument()); + + Document* document = editor->getDocument(); + context->setActiveDocument(document); // Start scroll loop if (msg->mouse.middle || clicked_ink->isScrollMovement()) { @@ -109,11 +153,39 @@ bool StandbyState::onMouseDown(Editor* editor, Message* msg) editor->setState(EditorStatePtr(new MovingCelState(editor, msg))); } } + return true; } + + // Transform selected pixels + if (document->isMaskVisible() && + m_decorator->getTransformHandles(editor)) { + TransformHandles* transfHandles = m_decorator->getTransformHandles(editor); + + // Get the handle covered by the mouse. + HandleType handle = transfHandles->getHandleAtPoint(editor, + gfx::Point(msg->mouse.x, msg->mouse.y), + document->getTransformation()); + + if (handle != NoHandle) { + int x, y, opacity; + Image* image = sprite->getCurrentImage(&x, &y, &opacity); + if (image) { + if (!sprite->getCurrentLayer()->is_writable()) { + Alert::show(PACKAGE "<setState(EditorStatePtr(new MovingPixelsState(editor, msg, image, x, y, opacity, handle))); + } + return true; + } + } + // Move selected pixels - else if (editor->isInsideSelection() && - current_tool->getInk(0)->isSelection() && - msg->mouse.left) { + if (editor->isInsideSelection() && + current_tool->getInk(0)->isSelection() && + msg->mouse.left) { int x, y, opacity; Image* image = sprite->getCurrentImage(&x, &y, &opacity); if (image) { @@ -123,11 +195,13 @@ bool StandbyState::onMouseDown(Editor* editor, Message* msg) } // Change to MovingPixelsState - editor->setState(EditorStatePtr(new MovingPixelsState(editor, msg, image, x, y, opacity))); + editor->setState(EditorStatePtr(new MovingPixelsState(editor, msg, image, x, y, opacity, NoHandle))); } + return true; } + // Call the eyedropper command - else if (clicked_ink->isEyedropper()) { + if (clicked_ink->isEyedropper()) { Command* eyedropper_cmd = CommandsModule::instance()->getCommandByName(CommandId::Eyedropper); @@ -137,11 +211,13 @@ bool StandbyState::onMouseDown(Editor* editor, Message* msg) UIContext::instance()->executeCommand(eyedropper_cmd, ¶ms); return true; } + // Start the Tool-Loop - else if (sprite->getCurrentLayer()) { + if (sprite->getCurrentLayer()) { tools::ToolLoop* toolLoop = create_tool_loop(editor, context, msg); if (toolLoop) editor->setState(EditorStatePtr(new DrawingState(toolLoop, editor, msg))); + return true; } return true; @@ -285,6 +361,10 @@ bool StandbyState::onSetCursor(Editor* editor) // If the current tool change selection (e.g. rectangular marquee, etc.) if (current_ink->isSelection()) { + // See if the cursor is in some selection handle. + if (m_decorator->onSetCursor(editor)) + return true; + // Move pixels if (editor->isInsideSelection()) { EditorCustomizationDelegate* customization = editor->getCustomizationDelegate(); @@ -381,3 +461,118 @@ bool StandbyState::onUpdateStatusBar(Editor* editor) return true; } + +gfx::Transformation StandbyState::getTransformation(Editor* editor) +{ + return editor->getDocument()->getTransformation(); +} + +////////////////////////////////////////////////////////////////////// +// Decorator + +StandbyState::Decorator::Decorator(StandbyState* standbyState) + : m_transfHandles(NULL) + , m_standbyState(standbyState) +{ +} + +StandbyState::Decorator::~Decorator() +{ + delete m_transfHandles; +} + +TransformHandles* StandbyState::Decorator::getTransformHandles(Editor* editor) +{ + if (!m_transfHandles) + m_transfHandles = new TransformHandles(); + + return m_transfHandles; +} + +bool StandbyState::Decorator::onSetCursor(Editor* editor) +{ + if (!editor->getDocument()->isMaskVisible()) + return false; + + const gfx::Transformation transformation(m_standbyState->getTransformation(editor)); + TransformHandles* tr = getTransformHandles(editor); + HandleType handle = tr->getHandleAtPoint(editor, + gfx::Point(jmouse_x(0), jmouse_y(0)), + transformation); + + int newCursor = JI_CURSOR_NORMAL; + + switch (handle) { + case ScaleNWHandle: newCursor = JI_CURSOR_SIZE_TL; break; + case ScaleNHandle: newCursor = JI_CURSOR_SIZE_T; break; + case ScaleNEHandle: newCursor = JI_CURSOR_SIZE_TR; break; + case ScaleWHandle: newCursor = JI_CURSOR_SIZE_L; break; + case ScaleEHandle: newCursor = JI_CURSOR_SIZE_R; break; + case ScaleSWHandle: newCursor = JI_CURSOR_SIZE_BL; break; + case ScaleSHandle: newCursor = JI_CURSOR_SIZE_B; break; + case ScaleSEHandle: newCursor = JI_CURSOR_SIZE_BR; break; + case RotateNWHandle: newCursor = JI_CURSOR_ROTATE_TL; break; + case RotateNHandle: newCursor = JI_CURSOR_ROTATE_T; break; + case RotateNEHandle: newCursor = JI_CURSOR_ROTATE_TR; break; + case RotateWHandle: newCursor = JI_CURSOR_ROTATE_L; break; + case RotateEHandle: newCursor = JI_CURSOR_ROTATE_R; break; + case RotateSWHandle: newCursor = JI_CURSOR_ROTATE_BL; break; + case RotateSHandle: newCursor = JI_CURSOR_ROTATE_B; break; + case RotateSEHandle: newCursor = JI_CURSOR_ROTATE_BR; break; + case PivotHandle: newCursor = JI_CURSOR_HAND; break; + default: + return false; + } + + // Adjust the cursor depending the current transformation angle. + fixed angle = ftofix(128.0 * transformation.angle() / PI); + angle = fixadd(angle, itofix(16)); + angle &= (255<<16); + angle >>= 16; + angle /= 32; + + if (newCursor >= JI_CURSOR_SIZE_TL && newCursor <= JI_CURSOR_SIZE_BR) { + 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 >= JI_CURSOR_ROTATE_TL && newCursor <= JI_CURSOR_ROTATE_BR) { + 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]; + } + + // Hide the drawing cursor (just in case) and show the new system cursor. + editor->hideDrawingCursor(); + jmouse_set_cursor(newCursor); + return true; +} + +void StandbyState::Decorator::preRenderDecorator(EditorPreRender* render) +{ + // Do nothing +} + +void StandbyState::Decorator::postRenderDecorator(EditorPostRender* render) +{ + Editor* editor = render->getEditor(); + + // Draw transformation handles (if the mask is visible and isn't frozen). + if (editor->getDocument()->isMaskVisible() && + !editor->getDocument()->getMask()->isFrozen()) { + // And draw only when the user has a selection tool as active tool. + tools::Tool* currentTool = editor->getCurrentEditorTool(); + + if (currentTool->getInk(0)->isSelection()) + getTransformHandles(editor)->drawHandles(editor, + m_standbyState->getTransformation(editor)); + } +} diff --git a/src/widgets/editor/standby_state.h b/src/widgets/editor/standby_state.h index c21ae16aa..9669e0f55 100644 --- a/src/widgets/editor/standby_state.h +++ b/src/widgets/editor/standby_state.h @@ -20,13 +20,19 @@ #define WIDGETS_EDITOR_STANDBY_STATE_H_INCLUDED #include "base/compiler_specific.h" +#include "gfx/transformation.h" +#include "widgets/editor/editor_decorator.h" #include "widgets/editor/editor_state.h" +class TransformHandles; + class StandbyState : public EditorState { public: StandbyState(); virtual ~StandbyState(); + virtual void onAfterChangeState(Editor* editor) OVERRIDE; + virtual void onCurrentToolChange(Editor* editor) OVERRIDE; virtual bool onMouseDown(Editor* editor, Message* msg) OVERRIDE; virtual bool onMouseUp(Editor* editor, Message* msg) OVERRIDE; virtual bool onMouseMove(Editor* editor, Message* msg) OVERRIDE; @@ -39,6 +45,30 @@ public: // Returns true as the standby state is the only one which shows the // pen-preview. virtual bool requirePenPreview() OVERRIDE { return true; } + + virtual gfx::Transformation getTransformation(Editor* editor); + +protected: + class Decorator : public EditorDecorator + { + public: + Decorator(StandbyState* standbyState); + virtual ~Decorator(); + + TransformHandles* getTransformHandles(Editor* editor); + + bool onSetCursor(Editor* editor); + + // EditorDecorator overrides + void preRenderDecorator(EditorPreRender* render) OVERRIDE; + void postRenderDecorator(EditorPostRender* render) OVERRIDE; + private: + TransformHandles* m_transfHandles; + StandbyState* m_standbyState; + }; + +private: + Decorator* m_decorator; }; #endif // WIDGETS_EDITOR_STANDBY_STATE_H_INCLUDED diff --git a/src/widgets/editor/transform_handles.cpp b/src/widgets/editor/transform_handles.cpp new file mode 100644 index 000000000..fd2615862 --- /dev/null +++ b/src/widgets/editor/transform_handles.cpp @@ -0,0 +1,234 @@ +/* ASE - Allegro Sprite Editor + * Copyright (C) 2001-2011 David Capello + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "config.h" + +#include "widgets/editor/transform_handles.h" + +#include "skin/skin_theme.h" +#include "widgets/editor/editor.h" + +#include + +static const int HANDLES = 8; + +static struct HandlesInfo { + // These indices are used to calculate the position of each handle. + // + // handle.x = (corners[i1].x + corners[i2].x) / 2 + // handle.y = (corners[i1].y + corners[i2].y) / 2 + // + // Corners reference (i1, i2): + // + // 0,0 0,1 1,1 + // 0,3 1,2 + // 3,3 3,2 2,2 + // + int i1, i2; + // The angle bias of this specific handle. + fixed angle; + // The exact handle type ([0] for scaling, [1] for rotating). + HandleType handle[2]; +} handles_info[HANDLES] = { + { 1, 2, 0 << 16, { ScaleEHandle, RotateEHandle } }, + { 1, 1, 32 << 16, { ScaleNEHandle, RotateNEHandle } }, + { 0, 1, 64 << 16, { ScaleNHandle, RotateNHandle } }, + { 0, 0, 96 << 16, { ScaleNWHandle, RotateNWHandle } }, + { 0, 3, 128 << 16, { ScaleWHandle, RotateWHandle } }, + { 3, 3, 160 << 16, { ScaleSWHandle, RotateSWHandle } }, + { 3, 2, 192 << 16, { ScaleSHandle, RotateSHandle } }, + { 2, 2, 224 << 16, { ScaleSEHandle, RotateSEHandle } }, +}; + +TransformHandles::TransformHandles() +{ +} + +TransformHandles::~TransformHandles() +{ +} + +HandleType TransformHandles::getHandleAtPoint(Editor* editor, const gfx::Point& pt, const gfx::Transformation& transform) +{ + SkinTheme* theme = static_cast(CurrentTheme::get()); + BITMAP* gfx = theme->get_part(PART_TRANSFORMATION_HANDLE); + fixed angle = ftofix(128.0 * transform.angle() / PI); + + gfx::Transformation::Corners corners; + transform.transformBox(corners); + + std::vector x(corners.size()); + std::vector y(corners.size()); + for (size_t c=0; ceditorToScreen(corners[c].x, corners[c].y, &x[c], &y[c]); + + int handle_rs[2] = { gfx->w*2, gfx->w*3 }; + for (int i=0; i<2; ++i) { + int handle_r = handle_rs[i]; + for (size_t c=0; c x(corners.size()); + std::vector y(corners.size()); + for (size_t c=0; ceditorToScreen(corners[c].x, corners[c].y, &x[c], &y[c]); + +#if 0 // Uncomment this if you want to see the bounds in red (only for debugging purposes) + // ----------------------------------------------- + { + int x1, y1, x2, y2; + x1 = transform.bounds().x; + y1 = transform.bounds().y; + x2 = x1 + transform.bounds().w; + y2 = y1 + transform.bounds().h; + editor->editorToScreen(x1, y1, &x1, &y1); + editor->editorToScreen(x2, y2, &x2, &y2); + rect(bmp, x1, y1, x2, y2, makecol(255, 0, 0)); + + x1 = transform.pivot().x; + y1 = transform.pivot().y; + editor->editorToScreen(x1, y1, &x1, &y1); + circle(bmp, x1, y1, 4, makecol(255, 0, 0)); + } + // ----------------------------------------------- +#endif + + // Draw corner handle + for (size_t c=0; c(CurrentTheme::get()); + BITMAP* gfx = theme->get_part(PART_PIVOT_HANDLE); + + draw_sprite_ex(bmp, gfx, pivotBounds.x, pivotBounds.y, DRAW_SPRITE_TRANS, DRAW_SPRITE_NO_FLIP); + } +} + +gfx::Rect TransformHandles::getPivotHandleBounds(Editor* editor, + const gfx::Transformation& transform, + const gfx::Transformation::Corners& corners) +{ + SkinTheme* theme = static_cast(CurrentTheme::get()); + BITMAP* gfx = theme->get_part(PART_PIVOT_HANDLE); + int pvx, pvy; + + editor->editorToScreen(transform.pivot().x, transform.pivot().y, &pvx, &pvy); + + pvx += (1 << editor->getZoom()) / 2; + pvy += (1 << editor->getZoom()) / 2; + + return gfx::Rect(pvx-gfx->w/2, pvy-gfx->h/2, gfx->w, gfx->h); +} + +bool TransformHandles::inHandle(const gfx::Point& pt, int x, int y, int gfx_w, int gfx_h, fixed angle) +{ + SkinTheme* theme = static_cast(CurrentTheme::get()); + BITMAP* gfx = theme->get_part(PART_TRANSFORMATION_HANDLE); + + adjustHandle(x, y, gfx_w, gfx_h, angle); + + return (pt.x >= x && pt.x < x+gfx_w && + pt.y >= y && pt.y < y+gfx_h); +} + +void TransformHandles::drawHandle(BITMAP* bmp, int x, int y, fixed angle) +{ + SkinTheme* theme = static_cast(CurrentTheme::get()); + BITMAP* gfx = theme->get_part(PART_TRANSFORMATION_HANDLE); + + adjustHandle(x, y, gfx->w, gfx->h, angle); + + draw_sprite_ex(bmp, gfx, x, y, DRAW_SPRITE_TRANS, DRAW_SPRITE_NO_FLIP); +} + +void TransformHandles::adjustHandle(int& x, int& y, int handle_w, int handle_h, fixed angle) +{ + angle = fixadd(angle, itofix(16)); + angle &= (255<<16); + angle >>= 16; + angle /= 32; + + // Adjust x,y position depending the angle of the handle + switch (angle) { + + case 0: + y = y-handle_h/2; + break; + + case 1: + y = y-handle_h; + break; + + case 2: + x = x-handle_w/2; + y = y-handle_h; + break; + + case 3: + x = x-handle_w; + y = y-handle_h; + break; + + case 4: + x = x-handle_w; + y = y-handle_h/2; + break; + + case 5: + x = x-handle_w; + break; + + case 6: + x = x-handle_w/2; + break; + + case 7: + // x and y are correct + break; + } +} diff --git a/src/widgets/editor/transform_handles.h b/src/widgets/editor/transform_handles.h new file mode 100644 index 000000000..a055f91f6 --- /dev/null +++ b/src/widgets/editor/transform_handles.h @@ -0,0 +1,55 @@ +/* ASE - Allegro Sprite Editor + * Copyright (C) 2001-2011 David Capello + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef WIDGETS_EDITOR_TRANSFORM_HANDLES_H_INCLUDED +#define WIDGETS_EDITOR_TRANSFORM_HANDLES_H_INCLUDED + +#include "gfx/point.h" +#include "gfx/transformation.h" +#include "widgets/editor/handle_type.h" + +#include + +class Editor; +struct BITMAP; + +// Helper class to do hit-detection and render transformation handles +// and rotation pivot. +class TransformHandles +{ +public: + TransformHandles(); + ~TransformHandles(); + + // Returns the handle in the given mouse point (pt) when the user + // has applied the given transformation to the selection. + HandleType getHandleAtPoint(Editor* editor, const gfx::Point& pt, const gfx::Transformation& transform); + + void drawHandles(Editor* editor, const gfx::Transformation& transform); + +private: + gfx::Rect getPivotHandleBounds(Editor* editor, + const gfx::Transformation& transform, + const gfx::Transformation::Corners& corners); + + bool inHandle(const gfx::Point& pt, int x, int y, int gfx_w, int gfx_h, fixed angle); + void drawHandle(BITMAP* bmp, int x, int y, fixed angle); + void adjustHandle(int& x, int& y, int handle_w, int handle_h, fixed angle); +}; + +#endif