mirror of
https://github.com/aseprite/aseprite.git
synced 2025-04-18 11:42:47 +00:00
Add skew transformation (fix #71)
This is the first version of the feature, it still needs some fixes (e.g. avoid skew transform when the pivot is in the same side of the skew handle which can calculate a division by zero).
This commit is contained in:
parent
98d06c31e3
commit
3fbdd40f24
Binary file not shown.
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 15 KiB |
@ -423,6 +423,14 @@
|
|||||||
<part id="tiles_manual" x="150" y="208" w="6" h="6" />
|
<part id="tiles_manual" x="150" y="208" w="6" h="6" />
|
||||||
<part id="tiles_auto" x="156" y="208" w="6" h="6" />
|
<part id="tiles_auto" x="156" y="208" w="6" h="6" />
|
||||||
<part id="tiles_stack" x="162" y="208" w="6" h="6" />
|
<part id="tiles_stack" x="162" y="208" w="6" h="6" />
|
||||||
|
<part id="cursor_skew_n" x="272" y="192" w="16" h="16" focusx="8" focusy="8" />
|
||||||
|
<part id="cursor_skew_s" x="288" y="192" w="16" h="16" focusx="8" focusy="8" />
|
||||||
|
<part id="cursor_skew_sw" x="272" y="208" w="16" h="16" focusx="8" focusy="8" />
|
||||||
|
<part id="cursor_skew_se" x="288" y="208" w="16" h="16" focusx="8" focusy="8" />
|
||||||
|
<part id="cursor_skew_w" x="272" y="176" w="16" h="16" focusx="8" focusy="8" />
|
||||||
|
<part id="cursor_skew_e" x="288" y="176" w="16" h="16" focusx="8" focusy="8" />
|
||||||
|
<part id="cursor_skew_nw" x="272" y="160" w="16" h="16" focusx="8" focusy="8" />
|
||||||
|
<part id="cursor_skew_ne" x="288" y="160" w="16" h="16" focusx="8" focusy="8" />
|
||||||
</parts>
|
</parts>
|
||||||
<styles>
|
<styles>
|
||||||
<style id="box" />
|
<style id="box" />
|
||||||
|
@ -590,6 +590,12 @@
|
|||||||
locks the movement to one axis (X or Y) -->
|
locks the movement to one axis (X or Y) -->
|
||||||
<key action="LockAxis" shortcut="Shift" />
|
<key action="LockAxis" shortcut="Shift" />
|
||||||
|
|
||||||
|
<!-- When we are moving the selection, pressing this shortcut
|
||||||
|
moves the selection to a subpixel level (floating point
|
||||||
|
coordinates). Useful for anti-aliasing effects. -->
|
||||||
|
<key action="FineControl" shortcut="Ctrl" context="TranslatingSelection" />
|
||||||
|
<key action="FineControl" shortcut="Ctrl" context="ScalingSelection" />
|
||||||
|
|
||||||
<!-- When you rotate the selection, pressing this
|
<!-- When you rotate the selection, pressing this
|
||||||
keyboard shortcut you activate angle snap -->
|
keyboard shortcut you activate angle snap -->
|
||||||
<key action="AngleSnap" shortcut="Shift" />
|
<key action="AngleSnap" shortcut="Shift" />
|
||||||
|
@ -58,4 +58,11 @@ void ExtraCel::create(const TilemapMode tilemapMode,
|
|||||||
m_cel->setFrame(frame);
|
m_cel->setFrame(frame);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ExtraCel::reset()
|
||||||
|
{
|
||||||
|
m_type = render::ExtraType::NONE;
|
||||||
|
m_image.reset();
|
||||||
|
m_cel.reset();
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace app
|
} // namespace app
|
||||||
|
@ -37,6 +37,7 @@ namespace app {
|
|||||||
const gfx::Size& imageSize,
|
const gfx::Size& imageSize,
|
||||||
const doc::frame_t frame,
|
const doc::frame_t frame,
|
||||||
const int opacity);
|
const int opacity);
|
||||||
|
void reset();
|
||||||
|
|
||||||
render::ExtraType type() const { return m_type; }
|
render::ExtraType type() const { return m_type; }
|
||||||
void setType(render::ExtraType type) { m_type = type; }
|
void setType(render::ExtraType type) { m_type = type; }
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
// Aseprite
|
// Aseprite
|
||||||
|
// Copyright (C) 2020 Igara Studio S.A.
|
||||||
// Copyright (C) 2001-2018 David Capello
|
// Copyright (C) 2001-2018 David Capello
|
||||||
//
|
//
|
||||||
// This program is distributed under the terms of
|
// This program is distributed under the terms of
|
||||||
@ -10,7 +11,6 @@
|
|||||||
|
|
||||||
#include "app/transformation.h"
|
#include "app/transformation.h"
|
||||||
|
|
||||||
#include "fixmath/fixmath.h"
|
|
||||||
#include "gfx/point.h"
|
#include "gfx/point.h"
|
||||||
#include "gfx/size.h"
|
#include "gfx/size.h"
|
||||||
|
|
||||||
@ -22,77 +22,66 @@ using namespace gfx;
|
|||||||
|
|
||||||
Transformation::Transformation()
|
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 RectF& bounds)
|
Transformation::Transformation(const RectF& bounds)
|
||||||
: m_bounds(bounds)
|
: m_bounds(bounds)
|
||||||
{
|
{
|
||||||
m_angle = 0.0;
|
|
||||||
m_pivot.x = bounds.x + bounds.w/2;
|
m_pivot.x = bounds.x + bounds.w/2;
|
||||||
m_pivot.y = bounds.y + bounds.h/2;
|
m_pivot.y = bounds.y + bounds.h/2;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Transformation::transformBox(Corners& corners) const
|
Transformation::Corners Transformation::transformedCorners() const
|
||||||
{
|
{
|
||||||
|
Corners corners(m_bounds);
|
||||||
|
|
||||||
// TODO We could create a composed 4x4 matrix with all
|
// TODO We could create a composed 4x4 matrix with all
|
||||||
// transformation and apply the same matrix to avoid calling
|
// transformation and apply the same matrix to avoid calling
|
||||||
// rotatePoint/cos/sin functions 4 times, anyway, it's not
|
// rotatePoint/cos/sin functions 4 times, anyway, it's not
|
||||||
// critical at this point.
|
// critical at this point.
|
||||||
|
|
||||||
corners = m_bounds;
|
|
||||||
for (std::size_t c=0; c<corners.size(); ++c)
|
for (std::size_t c=0; c<corners.size(); ++c)
|
||||||
corners[c] = Transformation::rotatePoint(corners[c], m_pivot, m_angle);
|
corners[c] = Transformation::rotatePoint(corners[c], m_pivot,
|
||||||
|
m_angle, m_skew);
|
||||||
|
return corners;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Transformation::displacePivotTo(const PointF& newPivot)
|
void Transformation::displacePivotTo(const PointF& newPivot)
|
||||||
{
|
{
|
||||||
// Calculate the rotated corners
|
// Calculate the rotated corners
|
||||||
Corners corners;
|
Corners corners = transformedCorners();
|
||||||
transformBox(corners);
|
|
||||||
|
|
||||||
// Rotate-back (-angle) the position of the rotated origin (corners[0])
|
// Rotate-back the position of the rotated origin (corners[0]) using
|
||||||
// using the new pivot.
|
// the new pivot.
|
||||||
PointF newBoundsOrigin =
|
PointF pt = corners.leftTop();
|
||||||
rotatePoint(corners.leftTop(),
|
pt = rotatePoint(pt, newPivot, -m_angle, 0.0);
|
||||||
newPivot,
|
pt = rotatePoint(pt, newPivot, 0.0, -m_skew);
|
||||||
-m_angle);
|
|
||||||
|
|
||||||
// Change the new pivot.
|
// Change the new pivot.
|
||||||
m_pivot = newPivot;
|
m_pivot = newPivot;
|
||||||
m_bounds = RectF(newBoundsOrigin, m_bounds.size());
|
m_bounds = RectF(pt, m_bounds.size());
|
||||||
}
|
}
|
||||||
|
|
||||||
PointF Transformation::rotatePoint(
|
PointF Transformation::rotatePoint(
|
||||||
const PointF& point,
|
const PointF& point,
|
||||||
const PointF& pivot,
|
const PointF& pivot,
|
||||||
double angle)
|
const double angle,
|
||||||
|
const double skew)
|
||||||
{
|
{
|
||||||
using namespace fixmath;
|
double cos = std::cos(-angle);
|
||||||
|
double sin = std::sin(-angle);
|
||||||
fixed fixangle = ftofix(-angle);
|
double tan = std::tan(skew);
|
||||||
fixangle = fixmul(fixangle, radtofix_r);
|
double dx = point.x - pivot.x;
|
||||||
|
double dy = point.y - pivot.y;
|
||||||
fixed cos = fixcos(fixangle);
|
dx += dy*tan;
|
||||||
fixed sin = fixsin(fixangle);
|
return PointF(pivot.x + dx*cos - dy*sin,
|
||||||
fixed dx = fixsub(ftofix(point.x), ftofix(pivot.x));
|
pivot.y + dx*sin + dy*cos);
|
||||||
fixed dy = fixsub(ftofix(point.y), ftofix(pivot.y));
|
|
||||||
return PointF(
|
|
||||||
fixtof(fixadd(ftofix(pivot.x), fixsub(fixmul(dx, cos), fixmul(dy, sin)))),
|
|
||||||
fixtof(fixadd(ftofix(pivot.y), fixadd(fixmul(dy, cos), fixmul(dx, sin)))));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
RectF Transformation::transformedBounds() const
|
RectF Transformation::transformedBounds() const
|
||||||
{
|
{
|
||||||
// Get transformed corners
|
// Get transformed corners
|
||||||
Corners corners;
|
Corners corners = transformedCorners();
|
||||||
transformBox(corners);
|
|
||||||
|
|
||||||
// Create a union of all corners
|
// Create a union of all corners
|
||||||
RectF bounds;
|
RectF bounds;
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
// Aseprite
|
// Aseprite
|
||||||
|
// Copyright (C) 2020 Igara Studio S.A.
|
||||||
// Copyright (C) 2001-2016 David Capello
|
// Copyright (C) 2001-2016 David Capello
|
||||||
//
|
//
|
||||||
// This program is distributed under the terms of
|
// This program is distributed under the terms of
|
||||||
@ -29,6 +30,16 @@ public:
|
|||||||
};
|
};
|
||||||
|
|
||||||
Corners() : m_corners(NUM_OF_CORNERS) { }
|
Corners() : m_corners(NUM_OF_CORNERS) { }
|
||||||
|
Corners(const gfx::RectF bounds) : m_corners(NUM_OF_CORNERS) {
|
||||||
|
m_corners[LEFT_TOP].x = bounds.x;
|
||||||
|
m_corners[LEFT_TOP].y = bounds.y;
|
||||||
|
m_corners[RIGHT_TOP].x = bounds.x2();
|
||||||
|
m_corners[RIGHT_TOP].y = bounds.y;
|
||||||
|
m_corners[RIGHT_BOTTOM].x = bounds.x2();
|
||||||
|
m_corners[RIGHT_BOTTOM].y = bounds.y2();
|
||||||
|
m_corners[LEFT_BOTTOM].x = bounds.x;
|
||||||
|
m_corners[LEFT_BOTTOM].y = bounds.y2();
|
||||||
|
}
|
||||||
|
|
||||||
std::size_t size() const { return m_corners.size(); }
|
std::size_t size() const { return m_corners.size(); }
|
||||||
|
|
||||||
@ -45,18 +56,6 @@ public:
|
|||||||
void rightBottom(const gfx::PointF& pt) { m_corners[RIGHT_BOTTOM] = pt; }
|
void rightBottom(const gfx::PointF& pt) { m_corners[RIGHT_BOTTOM] = pt; }
|
||||||
void leftBottom(const gfx::PointF& pt) { m_corners[LEFT_BOTTOM] = pt; }
|
void leftBottom(const gfx::PointF& pt) { m_corners[LEFT_BOTTOM] = pt; }
|
||||||
|
|
||||||
Corners& operator=(const gfx::RectF 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;
|
|
||||||
}
|
|
||||||
|
|
||||||
gfx::RectF bounds() const {
|
gfx::RectF bounds() const {
|
||||||
gfx::RectF bounds;
|
gfx::RectF bounds;
|
||||||
for (int i=0; i<Corners::NUM_OF_CORNERS; ++i)
|
for (int i=0; i<Corners::NUM_OF_CORNERS; ++i)
|
||||||
@ -76,14 +75,16 @@ public:
|
|||||||
const gfx::RectF& bounds() const { return m_bounds; }
|
const gfx::RectF& bounds() const { return m_bounds; }
|
||||||
const gfx::PointF& pivot() const { return m_pivot; }
|
const gfx::PointF& pivot() const { return m_pivot; }
|
||||||
double angle() const { return m_angle; }
|
double angle() const { return m_angle; }
|
||||||
|
double skew() const { return m_skew; }
|
||||||
|
|
||||||
void bounds(const gfx::RectF& bounds) { m_bounds = bounds; }
|
void bounds(const gfx::RectF& bounds) { m_bounds = bounds; }
|
||||||
void pivot(const gfx::PointF& pivot) { m_pivot = pivot; }
|
void pivot(const gfx::PointF& pivot) { m_pivot = pivot; }
|
||||||
void angle(double angle) { m_angle = angle; }
|
void angle(double angle) { m_angle = angle; }
|
||||||
|
void skew(double angle) { m_skew = angle; }
|
||||||
|
|
||||||
// Applies the transformation (rotation with angle/pivot) to the
|
// Applies the transformation (rotation with angle/pivot) to the
|
||||||
// current bounds (m_bounds).
|
// current bounds (m_bounds).
|
||||||
void transformBox(Corners& corners) const;
|
Corners transformedCorners() const;
|
||||||
|
|
||||||
// Changes the pivot to another location, adjusting the bounds to
|
// Changes the pivot to another location, adjusting the bounds to
|
||||||
// keep the current rotated-corners in the same location.
|
// keep the current rotated-corners in the same location.
|
||||||
@ -94,12 +95,14 @@ public:
|
|||||||
// Static helper method to rotate points.
|
// Static helper method to rotate points.
|
||||||
static gfx::PointF rotatePoint(const gfx::PointF& point,
|
static gfx::PointF rotatePoint(const gfx::PointF& point,
|
||||||
const gfx::PointF& pivot,
|
const gfx::PointF& pivot,
|
||||||
double angle);
|
const double angle,
|
||||||
|
const double skew);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
gfx::RectF m_bounds;
|
gfx::RectF m_bounds = gfx::RectF(0.0, 0.0, 0.0, 0.0);
|
||||||
gfx::PointF m_pivot;
|
gfx::PointF m_pivot = gfx::PointF(0.0, 0.0);
|
||||||
double m_angle;
|
double m_angle = 0.0;
|
||||||
|
double m_skew = 0.0;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace app
|
} // namespace app
|
||||||
|
@ -41,6 +41,7 @@
|
|||||||
#include "app/ui/dithering_selector.h"
|
#include "app/ui/dithering_selector.h"
|
||||||
#include "app/ui/dynamics_popup.h"
|
#include "app/ui/dynamics_popup.h"
|
||||||
#include "app/ui/editor/editor.h"
|
#include "app/ui/editor/editor.h"
|
||||||
|
#include "app/ui/expr_entry.h"
|
||||||
#include "app/ui/icon_button.h"
|
#include "app/ui/icon_button.h"
|
||||||
#include "app/ui/keyboard_shortcuts.h"
|
#include "app/ui/keyboard_shortcuts.h"
|
||||||
#include "app/ui/selection_mode_field.h"
|
#include "app/ui/selection_mode_field.h"
|
||||||
@ -48,6 +49,7 @@
|
|||||||
#include "app/ui_context.h"
|
#include "app/ui_context.h"
|
||||||
#include "base/clamp.h"
|
#include "base/clamp.h"
|
||||||
#include "base/fs.h"
|
#include "base/fs.h"
|
||||||
|
#include "base/pi.h"
|
||||||
#include "base/scoped_value.h"
|
#include "base/scoped_value.h"
|
||||||
#include "doc/brush.h"
|
#include "doc/brush.h"
|
||||||
#include "doc/image.h"
|
#include "doc/image.h"
|
||||||
@ -55,6 +57,7 @@
|
|||||||
#include "doc/remap.h"
|
#include "doc/remap.h"
|
||||||
#include "doc/selected_objects.h"
|
#include "doc/selected_objects.h"
|
||||||
#include "doc/slice.h"
|
#include "doc/slice.h"
|
||||||
|
#include "fmt/format.h"
|
||||||
#include "obs/connection.h"
|
#include "obs/connection.h"
|
||||||
#include "os/surface.h"
|
#include "os/surface.h"
|
||||||
#include "os/system.h"
|
#include "os/system.h"
|
||||||
@ -969,6 +972,156 @@ private:
|
|||||||
bool m_lockChange;
|
bool m_lockChange;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
class ContextBar::TransformationFields : public HBox {
|
||||||
|
public:
|
||||||
|
class CustomEntry : public ExprEntry {
|
||||||
|
public:
|
||||||
|
CustomEntry() {
|
||||||
|
setDecimals(1);
|
||||||
|
}
|
||||||
|
private:
|
||||||
|
bool onProcessMessage(Message* msg) override {
|
||||||
|
switch (msg->type()) {
|
||||||
|
|
||||||
|
case kKeyDownMessage:
|
||||||
|
if (ExprEntry::onProcessMessage(msg))
|
||||||
|
return true;
|
||||||
|
else if (hasFocus() && manager()->processFocusMovementMessage(msg))
|
||||||
|
return true;
|
||||||
|
return false;
|
||||||
|
|
||||||
|
case kFocusLeaveMessage: {
|
||||||
|
bool res = ExprEntry::onProcessMessage(msg);
|
||||||
|
deselectText();
|
||||||
|
setCaretPos(0);
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ExprEntry::onProcessMessage(msg);
|
||||||
|
}
|
||||||
|
void onVisible(bool visible) override {
|
||||||
|
if (!visible && hasFocus()) {
|
||||||
|
releaseFocus();
|
||||||
|
}
|
||||||
|
ExprEntry::onVisible(visible);
|
||||||
|
}
|
||||||
|
void onFormatExprFocusLeave(std::string& buf) override {
|
||||||
|
buf = formatDec(onGetTextDouble());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
TransformationFields() {
|
||||||
|
m_angle.setSuffix("°");
|
||||||
|
m_skew.setSuffix("°");
|
||||||
|
|
||||||
|
addChild(new Label("P:"));
|
||||||
|
addChild(&m_x);
|
||||||
|
addChild(&m_y);
|
||||||
|
addChild(&m_w);
|
||||||
|
addChild(&m_h);
|
||||||
|
addChild(new Label("R:"));
|
||||||
|
addChild(&m_angle);
|
||||||
|
addChild(&m_skew);
|
||||||
|
|
||||||
|
InitTheme.connect(
|
||||||
|
[this]{
|
||||||
|
gfx::Size sz(
|
||||||
|
font()->textLength("8")*4 + m_x.border().width(),
|
||||||
|
std::numeric_limits<int>::max());
|
||||||
|
setChildSpacing(0);
|
||||||
|
m_x.setMaxSize(sz);
|
||||||
|
m_y.setMaxSize(sz);
|
||||||
|
m_w.setMaxSize(sz);
|
||||||
|
m_h.setMaxSize(sz);
|
||||||
|
m_angle.setMaxSize(sz);
|
||||||
|
m_skew.setMaxSize(sz);
|
||||||
|
});
|
||||||
|
initTheme();
|
||||||
|
|
||||||
|
m_x.Change.connect([this]{ auto rc = bounds(); rc.x = m_x.textDouble(); onChangePos(rc); });
|
||||||
|
m_y.Change.connect([this]{ auto rc = bounds(); rc.y = m_y.textDouble(); onChangePos(rc); });
|
||||||
|
m_w.Change.connect([this]{ auto rc = bounds(); rc.w = m_w.textDouble(); onChangeSize(rc); });
|
||||||
|
m_h.Change.connect([this]{ auto rc = bounds(); rc.h = m_h.textDouble(); onChangeSize(rc); });
|
||||||
|
m_angle.Change.connect([this]{ onChangeAngle(); });
|
||||||
|
m_skew.Change.connect([this]{ onChangeSkew(); });
|
||||||
|
}
|
||||||
|
|
||||||
|
void update(const Transformation& t) {
|
||||||
|
auto rc = t.bounds();
|
||||||
|
|
||||||
|
m_x.setText(formatDec(rc.x));
|
||||||
|
m_y.setText(formatDec(rc.y));
|
||||||
|
m_w.setText(formatDec(rc.w));
|
||||||
|
m_h.setText(formatDec(rc.h));
|
||||||
|
m_angle.setText(formatDec(180.0 * t.angle() / PI));
|
||||||
|
m_skew.setText(formatDec(180.0 * t.skew() / PI));
|
||||||
|
|
||||||
|
m_t = t;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
static std::string formatDec(const double x) {
|
||||||
|
std::string s = fmt::format("{:0.1f}", x);
|
||||||
|
if (s.size() > 2 &&
|
||||||
|
s[s.size()-1] == '0' &&
|
||||||
|
s[s.size()-2] == '.') {
|
||||||
|
s.erase(s.size()-2, 2);
|
||||||
|
}
|
||||||
|
return s;
|
||||||
|
}
|
||||||
|
|
||||||
|
gfx::RectF bounds() const {
|
||||||
|
return m_t.bounds();
|
||||||
|
}
|
||||||
|
void onChangePos(gfx::RectF newBounds) {
|
||||||
|
// Adjust new pivot position depending on the new bounds origin
|
||||||
|
gfx::RectF bounds = m_t.bounds();
|
||||||
|
gfx::PointF pivot = m_t.pivot();
|
||||||
|
if (!bounds.isEmpty()) {
|
||||||
|
pivot.x = (pivot.x - bounds.x) / bounds.w;
|
||||||
|
pivot.y = (pivot.y - bounds.y) / bounds.h;
|
||||||
|
pivot.x = newBounds.x + pivot.x*newBounds.w;
|
||||||
|
pivot.y = newBounds.y + pivot.y*newBounds.h;
|
||||||
|
m_t.pivot(pivot);
|
||||||
|
}
|
||||||
|
m_t.bounds(newBounds);
|
||||||
|
updateEditor();
|
||||||
|
}
|
||||||
|
void onChangeSize(gfx::RectF newBounds) {
|
||||||
|
// Adjust bounds origin depending on the new size and the current pivot
|
||||||
|
gfx::RectF bounds = m_t.bounds();
|
||||||
|
gfx::PointF pivot = m_t.pivot();
|
||||||
|
if (!bounds.isEmpty()) {
|
||||||
|
pivot.x = (pivot.x - bounds.x) / bounds.w;
|
||||||
|
pivot.y = (pivot.y - bounds.y) / bounds.h;
|
||||||
|
newBounds.x -= (newBounds.w-bounds.w)*pivot.x;
|
||||||
|
newBounds.y -= (newBounds.h-bounds.h)*pivot.y;
|
||||||
|
|
||||||
|
m_x.setText(formatDec(newBounds.x));
|
||||||
|
m_y.setText(formatDec(newBounds.y));
|
||||||
|
}
|
||||||
|
m_t.bounds(newBounds);
|
||||||
|
updateEditor();
|
||||||
|
}
|
||||||
|
void onChangeAngle() {
|
||||||
|
m_t.angle(PI * m_angle.textDouble() / 180.0);
|
||||||
|
updateEditor();
|
||||||
|
}
|
||||||
|
void onChangeSkew() {
|
||||||
|
m_t.skew(PI * m_skew.textDouble() / 180.0);
|
||||||
|
updateEditor();
|
||||||
|
}
|
||||||
|
void updateEditor() {
|
||||||
|
if (current_editor)
|
||||||
|
current_editor->updateTransformation(m_t);
|
||||||
|
}
|
||||||
|
|
||||||
|
CustomEntry m_x, m_y, m_w, m_h;
|
||||||
|
CustomEntry m_angle;
|
||||||
|
CustomEntry m_skew;
|
||||||
|
Transformation m_t;
|
||||||
|
};
|
||||||
|
|
||||||
class ContextBar::DynamicsField : public ButtonSet
|
class ContextBar::DynamicsField : public ButtonSet
|
||||||
, public DynamicsPopup::Delegate {
|
, public DynamicsPopup::Delegate {
|
||||||
public:
|
public:
|
||||||
@ -1529,10 +1682,13 @@ private:
|
|||||||
ContextBar::ContextBar(TooltipManager* tooltipManager,
|
ContextBar::ContextBar(TooltipManager* tooltipManager,
|
||||||
ColorBar* colorBar)
|
ColorBar* colorBar)
|
||||||
{
|
{
|
||||||
|
auto& pref = Preferences::instance();
|
||||||
|
|
||||||
addChild(m_selectionOptionsBox = new HBox());
|
addChild(m_selectionOptionsBox = new HBox());
|
||||||
m_selectionOptionsBox->addChild(m_dropPixels = new DropPixelsField());
|
m_selectionOptionsBox->addChild(m_dropPixels = new DropPixelsField());
|
||||||
m_selectionOptionsBox->addChild(m_selectionMode = new SelectionModeField);
|
m_selectionOptionsBox->addChild(m_selectionMode = new SelectionModeField);
|
||||||
m_selectionOptionsBox->addChild(m_transparentColor = new TransparentColorField(this, tooltipManager));
|
m_selectionOptionsBox->addChild(m_transparentColor = new TransparentColorField(this, tooltipManager));
|
||||||
|
m_selectionOptionsBox->addChild(m_transformation = new TransformationFields);
|
||||||
m_selectionOptionsBox->addChild(m_pivot = new PivotField);
|
m_selectionOptionsBox->addChild(m_pivot = new PivotField);
|
||||||
m_selectionOptionsBox->addChild(m_rotAlgo = new RotAlgorithmField());
|
m_selectionOptionsBox->addChild(m_rotAlgo = new RotAlgorithmField());
|
||||||
|
|
||||||
@ -1573,7 +1729,7 @@ ContextBar::ContextBar(TooltipManager* tooltipManager,
|
|||||||
m_freehandBox->addChild(m_freehandAlgo = new FreehandAlgorithmField());
|
m_freehandBox->addChild(m_freehandAlgo = new FreehandAlgorithmField());
|
||||||
|
|
||||||
addChild(m_symmetry = new SymmetryField());
|
addChild(m_symmetry = new SymmetryField());
|
||||||
m_symmetry->setVisible(Preferences::instance().symmetryMode.enabled());
|
m_symmetry->setVisible(pref.symmetryMode.enabled());
|
||||||
|
|
||||||
addChild(m_sliceFields = new SliceFields);
|
addChild(m_sliceFields = new SliceFields);
|
||||||
|
|
||||||
@ -1582,7 +1738,6 @@ ContextBar::ContextBar(TooltipManager* tooltipManager,
|
|||||||
App::instance()->activeToolManager()->add_observer(this);
|
App::instance()->activeToolManager()->add_observer(this);
|
||||||
UIContext::instance()->add_observer(this);
|
UIContext::instance()->add_observer(this);
|
||||||
|
|
||||||
auto& pref = Preferences::instance();
|
|
||||||
pref.symmetryMode.enabled.AfterChange.connect(
|
pref.symmetryMode.enabled.AfterChange.connect(
|
||||||
[this]{ onSymmetryModeChange(); });
|
[this]{ onSymmetryModeChange(); });
|
||||||
pref.colorBar.fgColor.AfterChange.connect(
|
pref.colorBar.fgColor.AfterChange.connect(
|
||||||
@ -1927,6 +2082,7 @@ void ContextBar::updateForTool(tools::Tool* tool)
|
|||||||
m_selectionMode->setVisible(true);
|
m_selectionMode->setVisible(true);
|
||||||
m_pivot->setVisible(true);
|
m_pivot->setVisible(true);
|
||||||
m_dropPixels->setVisible(false);
|
m_dropPixels->setVisible(false);
|
||||||
|
m_transformation->setVisible(false);
|
||||||
m_selectBoxHelp->setVisible(false);
|
m_selectBoxHelp->setVisible(false);
|
||||||
|
|
||||||
m_symmetry->setVisible(
|
m_symmetry->setVisible(
|
||||||
@ -1943,7 +2099,7 @@ void ContextBar::updateForTool(tools::Tool* tool)
|
|||||||
layout();
|
layout();
|
||||||
}
|
}
|
||||||
|
|
||||||
void ContextBar::updateForMovingPixels()
|
void ContextBar::updateForMovingPixels(const Transformation& t)
|
||||||
{
|
{
|
||||||
tools::Tool* tool = App::instance()->toolBox()->getToolById(
|
tools::Tool* tool = App::instance()->toolBox()->getToolById(
|
||||||
tools::WellKnownTools::RectangularMarquee);
|
tools::WellKnownTools::RectangularMarquee);
|
||||||
@ -1953,6 +2109,8 @@ void ContextBar::updateForMovingPixels()
|
|||||||
m_dropPixels->deselectItems();
|
m_dropPixels->deselectItems();
|
||||||
m_dropPixels->setVisible(true);
|
m_dropPixels->setVisible(true);
|
||||||
m_selectionMode->setVisible(false);
|
m_selectionMode->setVisible(false);
|
||||||
|
m_transformation->setVisible(true);
|
||||||
|
m_transformation->update(t);
|
||||||
layout();
|
layout();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -53,6 +53,7 @@ namespace app {
|
|||||||
class ColorBar;
|
class ColorBar;
|
||||||
class DitheringSelector;
|
class DitheringSelector;
|
||||||
class GradientTypeSelector;
|
class GradientTypeSelector;
|
||||||
|
class Transformation;
|
||||||
|
|
||||||
class ContextBar : public DocObserverWidget<ui::HBox>
|
class ContextBar : public DocObserverWidget<ui::HBox>
|
||||||
, public obs::observable<ContextBarObserver>
|
, public obs::observable<ContextBarObserver>
|
||||||
@ -64,7 +65,7 @@ namespace app {
|
|||||||
|
|
||||||
void updateForActiveTool();
|
void updateForActiveTool();
|
||||||
void updateForTool(tools::Tool* tool);
|
void updateForTool(tools::Tool* tool);
|
||||||
void updateForMovingPixels();
|
void updateForMovingPixels(const Transformation& t);
|
||||||
void updateForSelectingBox(const std::string& text);
|
void updateForSelectingBox(const std::string& text);
|
||||||
void updateToolLoopModifiersIndicators(tools::ToolLoopModifiers modifiers);
|
void updateToolLoopModifiersIndicators(tools::ToolLoopModifiers modifiers);
|
||||||
void updateAutoSelectLayer(bool state);
|
void updateAutoSelectLayer(bool state);
|
||||||
@ -148,6 +149,7 @@ namespace app {
|
|||||||
class TransparentColorField;
|
class TransparentColorField;
|
||||||
class PivotField;
|
class PivotField;
|
||||||
class RotAlgorithmField;
|
class RotAlgorithmField;
|
||||||
|
class TransformationFields;
|
||||||
class DynamicsField;
|
class DynamicsField;
|
||||||
class FreehandAlgorithmField;
|
class FreehandAlgorithmField;
|
||||||
class BrushPatternField;
|
class BrushPatternField;
|
||||||
@ -187,6 +189,7 @@ namespace app {
|
|||||||
TransparentColorField* m_transparentColor;
|
TransparentColorField* m_transparentColor;
|
||||||
PivotField* m_pivot;
|
PivotField* m_pivot;
|
||||||
RotAlgorithmField* m_rotAlgo;
|
RotAlgorithmField* m_rotAlgo;
|
||||||
|
TransformationFields* m_transformation = nullptr;
|
||||||
DropPixelsField* m_dropPixels;
|
DropPixelsField* m_dropPixels;
|
||||||
doc::BrushRef m_activeBrush;
|
doc::BrushRef m_activeBrush;
|
||||||
ui::Label* m_selectBoxHelp;
|
ui::Label* m_selectBoxHelp;
|
||||||
|
@ -116,8 +116,8 @@ protected:
|
|||||||
case kKeyUpMessage:
|
case kKeyUpMessage:
|
||||||
if (static_cast<KeyMessage*>(msg)->repeat() == 0) {
|
if (static_cast<KeyMessage*>(msg)->repeat() == 0) {
|
||||||
KeyboardShortcuts* keys = KeyboardShortcuts::instance();
|
KeyboardShortcuts* keys = KeyboardShortcuts::instance();
|
||||||
KeyPtr lmb = keys->action(KeyAction::LeftMouseButton);
|
KeyPtr lmb = keys->action(KeyAction::LeftMouseButton, KeyContext::Any);
|
||||||
KeyPtr rmb = keys->action(KeyAction::RightMouseButton);
|
KeyPtr rmb = keys->action(KeyAction::RightMouseButton, KeyContext::Any);
|
||||||
|
|
||||||
// Convert action keys into mouse messages.
|
// Convert action keys into mouse messages.
|
||||||
if (lmb->isPressed(msg, *keys) ||
|
if (lmb->isPressed(msg, *keys) ||
|
||||||
|
@ -681,7 +681,8 @@ void Editor::drawOneSpriteUnclippedRect(ui::Graphics* g, const gfx::Rect& sprite
|
|||||||
}
|
}
|
||||||
|
|
||||||
ExtraCelRef extraCel = m_document->extraCel();
|
ExtraCelRef extraCel = m_document->extraCel();
|
||||||
if (extraCel && extraCel->type() != render::ExtraType::NONE) {
|
if (extraCel &&
|
||||||
|
extraCel->type() != render::ExtraType::NONE) {
|
||||||
m_renderEngine->setExtraImage(
|
m_renderEngine->setExtraImage(
|
||||||
extraCel->type(),
|
extraCel->type(),
|
||||||
extraCel->cel(),
|
extraCel->cel(),
|
||||||
@ -1307,7 +1308,7 @@ void Editor::flashCurrentLayer()
|
|||||||
return;
|
return;
|
||||||
|
|
||||||
Site site = getSite();
|
Site site = getSite();
|
||||||
if (const Cel* src_cel = site.cel()) {
|
if (site.cel()) {
|
||||||
// Hide and destroy the extra cel used by the brush preview
|
// Hide and destroy the extra cel used by the brush preview
|
||||||
// because we'll need to use the extra cel now for the flashing
|
// because we'll need to use the extra cel now for the flashing
|
||||||
// layer.
|
// layer.
|
||||||
@ -2514,7 +2515,7 @@ void Editor::pasteImage(const Image* image, const Mask* mask)
|
|||||||
void Editor::startSelectionTransformation(const gfx::Point& move, double angle)
|
void Editor::startSelectionTransformation(const gfx::Point& move, double angle)
|
||||||
{
|
{
|
||||||
if (auto movingPixels = dynamic_cast<MovingPixelsState*>(m_state.get())) {
|
if (auto movingPixels = dynamic_cast<MovingPixelsState*>(m_state.get())) {
|
||||||
movingPixels->translate(move);
|
movingPixels->translate(gfx::PointF(move));
|
||||||
if (std::fabs(angle) > 1e-5)
|
if (std::fabs(angle) > 1e-5)
|
||||||
movingPixels->rotate(angle);
|
movingPixels->rotate(angle);
|
||||||
}
|
}
|
||||||
@ -2531,6 +2532,12 @@ void Editor::startFlipTransformation(doc::algorithm::FlipType flipType)
|
|||||||
standby->startFlipTransformation(this, flipType);
|
standby->startFlipTransformation(this, flipType);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Editor::updateTransformation(const Transformation& transform)
|
||||||
|
{
|
||||||
|
if (auto movingPixels = dynamic_cast<MovingPixelsState*>(m_state.get()))
|
||||||
|
movingPixels->updateTransformation(transform);
|
||||||
|
}
|
||||||
|
|
||||||
void Editor::notifyScrollChanged()
|
void Editor::notifyScrollChanged()
|
||||||
{
|
{
|
||||||
m_observers.notifyScrollChanged(this);
|
m_observers.notifyScrollChanged(this);
|
||||||
|
@ -60,6 +60,7 @@ namespace app {
|
|||||||
class EditorRender;
|
class EditorRender;
|
||||||
class PixelsMovement;
|
class PixelsMovement;
|
||||||
class Site;
|
class Site;
|
||||||
|
class Transformation;
|
||||||
|
|
||||||
namespace tools {
|
namespace tools {
|
||||||
class Ink;
|
class Ink;
|
||||||
@ -242,8 +243,8 @@ namespace app {
|
|||||||
void pasteImage(const Image* image, const Mask* mask = nullptr);
|
void pasteImage(const Image* image, const Mask* mask = nullptr);
|
||||||
|
|
||||||
void startSelectionTransformation(const gfx::Point& move, double angle);
|
void startSelectionTransformation(const gfx::Point& move, double angle);
|
||||||
|
|
||||||
void startFlipTransformation(doc::algorithm::FlipType flipType);
|
void startFlipTransformation(doc::algorithm::FlipType flipType);
|
||||||
|
void updateTransformation(const Transformation& transform);
|
||||||
|
|
||||||
// Used by EditorView to notify changes in the view's scroll
|
// Used by EditorView to notify changes in the view's scroll
|
||||||
// position.
|
// position.
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
// Aseprite
|
// Aseprite
|
||||||
|
// Copyright (C) 2020 Igara Studio S.A.
|
||||||
// Copyright (C) 2001-2017 David Capello
|
// Copyright (C) 2001-2017 David Capello
|
||||||
//
|
//
|
||||||
// This program is distributed under the terms of
|
// This program is distributed under the terms of
|
||||||
@ -22,10 +23,12 @@ namespace app {
|
|||||||
ScaleNWHandle, ScaleNHandle, ScaleNEHandle,
|
ScaleNWHandle, ScaleNHandle, ScaleNEHandle,
|
||||||
ScaleWHandle, ScaleEHandle,
|
ScaleWHandle, ScaleEHandle,
|
||||||
ScaleSWHandle, ScaleSHandle, ScaleSEHandle,
|
ScaleSWHandle, ScaleSHandle, ScaleSEHandle,
|
||||||
// One of the region's corders to rotate.
|
// Rotate from corners
|
||||||
RotateNWHandle, RotateNHandle, RotateNEHandle,
|
RotateNWHandle, RotateNEHandle,
|
||||||
RotateWHandle, RotateEHandle,
|
RotateSWHandle, RotateSEHandle,
|
||||||
RotateSWHandle, RotateSHandle, RotateSEHandle,
|
// Skew from sides
|
||||||
|
SkewNHandle, SkewWHandle,
|
||||||
|
SkewEHandle, SkewSHandle,
|
||||||
// Handle used to move the pivot
|
// Handle used to move the pivot
|
||||||
PivotHandle,
|
PivotHandle,
|
||||||
};
|
};
|
||||||
|
@ -26,6 +26,7 @@
|
|||||||
#include "app/pref/preferences.h"
|
#include "app/pref/preferences.h"
|
||||||
#include "app/tools/ink.h"
|
#include "app/tools/ink.h"
|
||||||
#include "app/tools/tool.h"
|
#include "app/tools/tool.h"
|
||||||
|
#include "app/transformation.h"
|
||||||
#include "app/ui/context_bar.h"
|
#include "app/ui/context_bar.h"
|
||||||
#include "app/ui/editor/editor.h"
|
#include "app/ui/editor/editor.h"
|
||||||
#include "app/ui/editor/editor_customization_delegate.h"
|
#include "app/ui/editor/editor_customization_delegate.h"
|
||||||
@ -63,9 +64,9 @@ MovingPixelsState::MovingPixelsState(Editor* editor, MouseMessage* msg, PixelsMo
|
|||||||
, m_observingEditor(false)
|
, m_observingEditor(false)
|
||||||
, m_discarded(false)
|
, m_discarded(false)
|
||||||
, m_renderTimer(50)
|
, m_renderTimer(50)
|
||||||
, m_oldSpritePos(std::numeric_limits<int>::min(),
|
|
||||||
std::numeric_limits<int>::min())
|
|
||||||
{
|
{
|
||||||
|
m_pixelsMovement->setDelegate(this);
|
||||||
|
|
||||||
// MovingPixelsState needs a selection tool to avoid problems
|
// MovingPixelsState needs a selection tool to avoid problems
|
||||||
// sharing the extra cel between the drawing cursor preview and the
|
// sharing the extra cel between the drawing cursor preview and the
|
||||||
// pixels movement/transformation preview.
|
// pixels movement/transformation preview.
|
||||||
@ -75,7 +76,7 @@ MovingPixelsState::MovingPixelsState(Editor* editor, MouseMessage* msg, PixelsMo
|
|||||||
|
|
||||||
if (handle != NoHandle) {
|
if (handle != NoHandle) {
|
||||||
gfx::Point pt = editor->screenToEditor(msg->position());
|
gfx::Point pt = editor->screenToEditor(msg->position());
|
||||||
m_pixelsMovement->catchImage(pt, handle);
|
m_pixelsMovement->catchImage(gfx::PointF(pt), handle);
|
||||||
|
|
||||||
editor->captureMouse();
|
editor->captureMouse();
|
||||||
}
|
}
|
||||||
@ -111,7 +112,7 @@ MovingPixelsState::MovingPixelsState(Editor* editor, MouseMessage* msg, PixelsMo
|
|||||||
m_observingEditor = true;
|
m_observingEditor = true;
|
||||||
|
|
||||||
ContextBar* contextBar = App::instance()->contextBar();
|
ContextBar* contextBar = App::instance()->contextBar();
|
||||||
contextBar->updateForMovingPixels();
|
contextBar->updateForMovingPixels(getTransformation(editor));
|
||||||
contextBar->add_observer(this);
|
contextBar->add_observer(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -131,12 +132,12 @@ MovingPixelsState::~MovingPixelsState()
|
|||||||
m_editor->document()->generateMaskBoundaries();
|
m_editor->document()->generateMaskBoundaries();
|
||||||
}
|
}
|
||||||
|
|
||||||
void MovingPixelsState::translate(const gfx::Point& delta)
|
void MovingPixelsState::translate(const gfx::PointF& delta)
|
||||||
{
|
{
|
||||||
if (m_pixelsMovement->isDragging())
|
if (m_pixelsMovement->isDragging())
|
||||||
m_pixelsMovement->dropImageTemporarily();
|
m_pixelsMovement->dropImageTemporarily();
|
||||||
|
|
||||||
m_pixelsMovement->catchImageAgain(gfx::Point(0, 0), MovePixelsHandle);
|
m_pixelsMovement->catchImageAgain(gfx::PointF(0, 0), MovePixelsHandle);
|
||||||
m_pixelsMovement->moveImage(delta, PixelsMovement::NormalMovement);
|
m_pixelsMovement->moveImage(delta, PixelsMovement::NormalMovement);
|
||||||
m_pixelsMovement->dropImageTemporarily();
|
m_pixelsMovement->dropImageTemporarily();
|
||||||
m_editor->updateStatusBar();
|
m_editor->updateStatusBar();
|
||||||
@ -160,6 +161,12 @@ void MovingPixelsState::shift(int dx, int dy)
|
|||||||
m_editor->updateStatusBar();
|
m_editor->updateStatusBar();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void MovingPixelsState::updateTransformation(const Transformation& t)
|
||||||
|
{
|
||||||
|
m_pixelsMovement->setTransformation(t);
|
||||||
|
m_editor->updateStatusBar();
|
||||||
|
}
|
||||||
|
|
||||||
void MovingPixelsState::onEnterState(Editor* editor)
|
void MovingPixelsState::onEnterState(Editor* editor)
|
||||||
{
|
{
|
||||||
StandbyState::onEnterState(editor);
|
StandbyState::onEnterState(editor);
|
||||||
@ -174,7 +181,7 @@ void MovingPixelsState::onEditorGotFocus(Editor* editor)
|
|||||||
// when we are back to an editor in MovingPixelsState. Without this
|
// when we are back to an editor in MovingPixelsState. Without this
|
||||||
// we would see the SelectionModeField instead which doesn't make
|
// we would see the SelectionModeField instead which doesn't make
|
||||||
// sense on MovingPixelsState).
|
// sense on MovingPixelsState).
|
||||||
contextBar->updateForMovingPixels();
|
contextBar->updateForMovingPixels(getTransformation(editor));
|
||||||
}
|
}
|
||||||
|
|
||||||
EditorState::LeaveAction MovingPixelsState::onLeaveState(Editor* editor, EditorState* newState)
|
EditorState::LeaveAction MovingPixelsState::onLeaveState(Editor* editor, EditorState* newState)
|
||||||
@ -255,7 +262,7 @@ void MovingPixelsState::onActiveToolChange(Editor* editor, tools::Tool* tool)
|
|||||||
else if (tool->getInk(0)->isSelection() ||
|
else if (tool->getInk(0)->isSelection() ||
|
||||||
tool->getInk(1)->isSelection()) {
|
tool->getInk(1)->isSelection()) {
|
||||||
ContextBar* contextBar = App::instance()->contextBar();
|
ContextBar* contextBar = App::instance()->contextBar();
|
||||||
contextBar->updateForMovingPixels();
|
contextBar->updateForMovingPixels(getTransformation(editor));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -273,7 +280,7 @@ bool MovingPixelsState::onMouseDown(Editor* editor, MouseMessage* msg)
|
|||||||
ctx->setActiveView(editor->getDocView());
|
ctx->setActiveView(editor->getDocView());
|
||||||
|
|
||||||
ContextBar* contextBar = App::instance()->contextBar();
|
ContextBar* contextBar = App::instance()->contextBar();
|
||||||
contextBar->updateForMovingPixels();
|
contextBar->updateForMovingPixels(getTransformation(editor));
|
||||||
|
|
||||||
// Start scroll loop
|
// Start scroll loop
|
||||||
if (editor->checkForScroll(msg) ||
|
if (editor->checkForScroll(msg) ||
|
||||||
@ -307,8 +314,8 @@ bool MovingPixelsState::onMouseDown(Editor* editor, MouseMessage* msg)
|
|||||||
return true;
|
return true;
|
||||||
|
|
||||||
// Re-catch the image
|
// Re-catch the image
|
||||||
m_pixelsMovement->catchImageAgain(
|
gfx::Point pt = editor->screenToEditor(msg->position());
|
||||||
editor->screenToEditor(msg->position()), handle);
|
m_pixelsMovement->catchImageAgain(gfx::PointF(pt), handle);
|
||||||
|
|
||||||
editor->captureMouse();
|
editor->captureMouse();
|
||||||
return true;
|
return true;
|
||||||
@ -332,7 +339,7 @@ bool MovingPixelsState::onMouseDown(Editor* editor, MouseMessage* msg)
|
|||||||
|
|
||||||
// Re-catch the image
|
// Re-catch the image
|
||||||
m_pixelsMovement->catchImageAgain(
|
m_pixelsMovement->catchImageAgain(
|
||||||
editor->screenToEditor(msg->position()), MovePixelsHandle);
|
editor->screenToEditorF(msg->position()), MovePixelsHandle);
|
||||||
|
|
||||||
editor->captureMouse();
|
editor->captureMouse();
|
||||||
return true;
|
return true;
|
||||||
@ -373,17 +380,7 @@ bool MovingPixelsState::onMouseMove(Editor* editor, MouseMessage* msg)
|
|||||||
gfx::Point mousePos = editor->autoScroll(msg, AutoScroll::MouseDir);
|
gfx::Point mousePos = editor->autoScroll(msg, AutoScroll::MouseDir);
|
||||||
|
|
||||||
// Get the position of the mouse in the sprite
|
// Get the position of the mouse in the sprite
|
||||||
gfx::Point spritePos = editor->screenToEditor(mousePos);
|
gfx::PointF spritePos = editor->screenToEditorF(mousePos);
|
||||||
if (spritePos == m_oldSpritePos) {
|
|
||||||
// Avoid redrawing everything if the position in the canvas didn't change.
|
|
||||||
// TODO remove this if we add support for anti-aliasing in the
|
|
||||||
// transformations
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
m_oldSpritePos = spritePos;
|
|
||||||
|
|
||||||
m_renderTimer.start();
|
|
||||||
m_pixelsMovement->setFastMode(true);
|
|
||||||
|
|
||||||
// Get the customization for the pixels movement (snap to grid, angle snap, etc.).
|
// Get the customization for the pixels movement (snap to grid, angle snap, etc.).
|
||||||
KeyContext keyContext = KeyContext::Normal;
|
KeyContext keyContext = KeyContext::Normal;
|
||||||
@ -402,15 +399,17 @@ bool MovingPixelsState::onMouseMove(Editor* editor, MouseMessage* msg)
|
|||||||
keyContext = KeyContext::ScalingSelection;
|
keyContext = KeyContext::ScalingSelection;
|
||||||
break;
|
break;
|
||||||
case RotateNWHandle:
|
case RotateNWHandle:
|
||||||
case RotateNHandle:
|
|
||||||
case RotateNEHandle:
|
case RotateNEHandle:
|
||||||
case RotateWHandle:
|
|
||||||
case RotateEHandle:
|
|
||||||
case RotateSWHandle:
|
case RotateSWHandle:
|
||||||
case RotateSHandle:
|
|
||||||
case RotateSEHandle:
|
case RotateSEHandle:
|
||||||
keyContext = KeyContext::RotatingSelection;
|
keyContext = KeyContext::RotatingSelection;
|
||||||
break;
|
break;
|
||||||
|
case SkewNHandle:
|
||||||
|
case SkewWHandle:
|
||||||
|
case SkewEHandle:
|
||||||
|
case SkewSHandle:
|
||||||
|
keyContext = KeyContext::ScalingSelection;
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
PixelsMovement::MoveModifier moveModifier = PixelsMovement::NormalMovement;
|
PixelsMovement::MoveModifier moveModifier = PixelsMovement::NormalMovement;
|
||||||
@ -432,14 +431,25 @@ bool MovingPixelsState::onMouseMove(Editor* editor, MouseMessage* msg)
|
|||||||
if (int(action & KeyAction::LockAxis))
|
if (int(action & KeyAction::LockAxis))
|
||||||
moveModifier |= PixelsMovement::LockAxisMovement;
|
moveModifier |= PixelsMovement::LockAxisMovement;
|
||||||
|
|
||||||
|
if (int(action & KeyAction::FineControl))
|
||||||
|
moveModifier |= PixelsMovement::FineControl;
|
||||||
|
|
||||||
|
m_renderTimer.start();
|
||||||
|
m_pixelsMovement->setFastMode(true);
|
||||||
|
|
||||||
// Invalidate handles
|
// Invalidate handles
|
||||||
Decorator* decorator = static_cast<Decorator*>(editor->decorator());
|
Decorator* decorator = static_cast<Decorator*>(editor->decorator());
|
||||||
TransformHandles* transfHandles = decorator->getTransformHandles(editor);
|
TransformHandles* transfHandles = decorator->getTransformHandles(editor);
|
||||||
transfHandles->invalidateHandles(editor, m_pixelsMovement->getTransformation());
|
const Transformation& transformation = m_pixelsMovement->getTransformation();
|
||||||
|
transfHandles->invalidateHandles(editor, transformation);
|
||||||
|
|
||||||
// Drag the image to that position
|
// Drag the image to that position
|
||||||
m_pixelsMovement->moveImage(spritePos, moveModifier);
|
m_pixelsMovement->moveImage(spritePos, moveModifier);
|
||||||
|
|
||||||
|
// Update context bar and status bar
|
||||||
|
ContextBar* contextBar = App::instance()->contextBar();
|
||||||
|
contextBar->updateForMovingPixels(transformation);
|
||||||
|
|
||||||
editor->updateStatusBar();
|
editor->updateStatusBar();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@ -586,7 +596,7 @@ void MovingPixelsState::onBeforeCommandExecution(CommandExecutionEvent& ev)
|
|||||||
m_pixelsMovement->shift(delta.x, delta.y);
|
m_pixelsMovement->shift(delta.x, delta.y);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
translate(delta);
|
translate(gfx::PointF(delta));
|
||||||
}
|
}
|
||||||
// We've processed the selection content movement right here.
|
// We've processed the selection content movement right here.
|
||||||
ev.cancel();
|
ev.cancel();
|
||||||
@ -751,6 +761,12 @@ void MovingPixelsState::onDropPixels(ContextBarObserver::DropAction action)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void MovingPixelsState::onPivotChange()
|
||||||
|
{
|
||||||
|
ContextBar* contextBar = App::instance()->contextBar();
|
||||||
|
contextBar->updateForMovingPixels(getTransformation(m_editor));
|
||||||
|
}
|
||||||
|
|
||||||
void MovingPixelsState::setTransparentColor(bool opaque, const app::Color& color)
|
void MovingPixelsState::setTransparentColor(bool opaque, const app::Color& color)
|
||||||
{
|
{
|
||||||
ASSERT(m_pixelsMovement);
|
ASSERT(m_pixelsMovement);
|
||||||
|
@ -29,7 +29,8 @@ namespace app {
|
|||||||
class MovingPixelsState
|
class MovingPixelsState
|
||||||
: public StandbyState
|
: public StandbyState
|
||||||
, EditorObserver
|
, EditorObserver
|
||||||
, ContextBarObserver {
|
, ContextBarObserver
|
||||||
|
, PixelsMovementDelegate {
|
||||||
public:
|
public:
|
||||||
MovingPixelsState(Editor* editor, ui::MouseMessage* msg, PixelsMovementPtr pixelsMovement, HandleType handle);
|
MovingPixelsState(Editor* editor, ui::MouseMessage* msg, PixelsMovementPtr pixelsMovement, HandleType handle);
|
||||||
virtual ~MovingPixelsState();
|
virtual ~MovingPixelsState();
|
||||||
@ -38,11 +39,13 @@ namespace app {
|
|||||||
return m_pixelsMovement->canHandleFrameChange();
|
return m_pixelsMovement->canHandleFrameChange();
|
||||||
}
|
}
|
||||||
|
|
||||||
void translate(const gfx::Point& delta);
|
void translate(const gfx::PointF& delta);
|
||||||
void rotate(double angle);
|
void rotate(double angle);
|
||||||
void flip(doc::algorithm::FlipType flipType);
|
void flip(doc::algorithm::FlipType flipType);
|
||||||
void shift(int dx, int dy);
|
void shift(int dx, int dy);
|
||||||
|
|
||||||
|
void updateTransformation(const Transformation& t);
|
||||||
|
|
||||||
// EditorState
|
// EditorState
|
||||||
virtual void onEnterState(Editor* editor) override;
|
virtual void onEnterState(Editor* editor) override;
|
||||||
virtual void onEditorGotFocus(Editor* editor) override;
|
virtual void onEditorGotFocus(Editor* editor) override;
|
||||||
@ -66,6 +69,9 @@ namespace app {
|
|||||||
// ContextBarObserver
|
// ContextBarObserver
|
||||||
virtual void onDropPixels(ContextBarObserver::DropAction action) override;
|
virtual void onDropPixels(ContextBarObserver::DropAction action) override;
|
||||||
|
|
||||||
|
// PixelsMovementDelegate
|
||||||
|
virtual void onPivotChange() override;
|
||||||
|
|
||||||
virtual Transformation getTransformation(Editor* editor) override;
|
virtual Transformation getTransformation(Editor* editor) override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
@ -98,7 +104,7 @@ namespace app {
|
|||||||
// Position of the mouse in the canvas to avoid redrawing when the
|
// Position of the mouse in the canvas to avoid redrawing when the
|
||||||
// mouse position changes (only we redraw when the canvas position
|
// mouse position changes (only we redraw when the canvas position
|
||||||
// changes).
|
// changes).
|
||||||
gfx::Point m_oldSpritePos;
|
gfx::PointF m_oldSpritePos;
|
||||||
|
|
||||||
obs::connection m_ctxConn;
|
obs::connection m_ctxConn;
|
||||||
obs::connection m_opaqueConn;
|
obs::connection m_opaqueConn;
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
// Aseprite
|
// Aseprite
|
||||||
|
// Copyright (C) 2020 Igara Studio S.A.
|
||||||
// Copyright (C) 2001-2016 David Capello
|
// Copyright (C) 2001-2016 David Capello
|
||||||
//
|
//
|
||||||
// This program is distributed under the terms of
|
// This program is distributed under the terms of
|
||||||
@ -17,8 +18,7 @@ namespace app {
|
|||||||
|
|
||||||
void set_pivot_from_preferences(Transformation& t)
|
void set_pivot_from_preferences(Transformation& t)
|
||||||
{
|
{
|
||||||
Transformation::Corners corners;
|
auto corners = t.transformedCorners();
|
||||||
t.transformBox(corners);
|
|
||||||
gfx::PointT<double> nw(corners[Transformation::Corners::LEFT_TOP]);
|
gfx::PointT<double> nw(corners[Transformation::Corners::LEFT_TOP]);
|
||||||
gfx::PointT<double> ne(corners[Transformation::Corners::RIGHT_TOP]);
|
gfx::PointT<double> ne(corners[Transformation::Corners::RIGHT_TOP]);
|
||||||
gfx::PointT<double> sw(corners[Transformation::Corners::LEFT_BOTTOM]);
|
gfx::PointT<double> sw(corners[Transformation::Corners::LEFT_BOTTOM]);
|
||||||
|
@ -53,11 +53,17 @@
|
|||||||
#define DUMP_INNER_CMDS()
|
#define DUMP_INNER_CMDS()
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
using vec2 = base::Vector2d<double>;
|
||||||
|
|
||||||
namespace app {
|
namespace app {
|
||||||
|
|
||||||
template<typename T>
|
template<typename T>
|
||||||
static inline const base::Vector2d<double> point2Vector(const gfx::PointT<T>& pt) {
|
static inline const vec2 to_vec2(const gfx::PointT<T>& pt) {
|
||||||
return base::Vector2d<double>(pt.x, pt.y);
|
return vec2(pt.x, pt.y);
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline const gfx::PointF to_point(const vec2& v) {
|
||||||
|
return gfx::PointF(v.x, v.y);
|
||||||
}
|
}
|
||||||
|
|
||||||
PixelsMovement::InnerCmd::InnerCmd(InnerCmd&& c)
|
PixelsMovement::InnerCmd::InnerCmd(InnerCmd&& c)
|
||||||
@ -180,6 +186,11 @@ bool PixelsMovement::editMultipleCels() const
|
|||||||
m_site.range().type() == DocRange::kCels));
|
m_site.range().type() == DocRange::kCels));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void PixelsMovement::setDelegate(PixelsMovementDelegate* delegate)
|
||||||
|
{
|
||||||
|
m_delegate = delegate;
|
||||||
|
}
|
||||||
|
|
||||||
void PixelsMovement::setFastMode(const bool fastMode)
|
void PixelsMovement::setFastMode(const bool fastMode)
|
||||||
{
|
{
|
||||||
bool redraw = (m_fastMode && !fastMode);
|
bool redraw = (m_fastMode && !fastMode);
|
||||||
@ -243,6 +254,49 @@ void PixelsMovement::shift(int dx, int dy)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void PixelsMovement::setTransformation(const Transformation& t)
|
||||||
|
{
|
||||||
|
m_initialData = m_currentData;
|
||||||
|
|
||||||
|
setTransformationBase(t);
|
||||||
|
|
||||||
|
redrawCurrentMask();
|
||||||
|
updateDocumentMask();
|
||||||
|
|
||||||
|
update_screen_for_document(m_document);
|
||||||
|
}
|
||||||
|
|
||||||
|
void PixelsMovement::setTransformationBase(const Transformation& t)
|
||||||
|
{
|
||||||
|
// Get old transformed corners, update transformation, and get new
|
||||||
|
// transformed corners. These corners will be used to know what to
|
||||||
|
// update in the editor's canvas.
|
||||||
|
auto oldCorners = m_currentData.transformedCorners();
|
||||||
|
m_currentData = t;
|
||||||
|
auto newCorners = m_currentData.transformedCorners();
|
||||||
|
|
||||||
|
redrawExtraImage();
|
||||||
|
|
||||||
|
m_document->setTransformation(m_currentData);
|
||||||
|
|
||||||
|
// Create a union of all corners, and that will be the bounds to
|
||||||
|
// redraw of the sprite.
|
||||||
|
gfx::Rect fullBounds;
|
||||||
|
for (int i=0; i<Transformation::Corners::NUM_OF_CORNERS; ++i) {
|
||||||
|
fullBounds |= gfx::Rect((int)oldCorners[i].x, (int)oldCorners[i].y, 1, 1);
|
||||||
|
fullBounds |= gfx::Rect((int)newCorners[i].x, (int)newCorners[i].y, 1, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// If "fullBounds" is empty is because the cel was not moved
|
||||||
|
if (!fullBounds.isEmpty()) {
|
||||||
|
// Notify the modified region.
|
||||||
|
m_document->notifySpritePixelsModified(
|
||||||
|
m_site.sprite(),
|
||||||
|
gfx::Region(fullBounds),
|
||||||
|
m_site.frame());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void PixelsMovement::trim()
|
void PixelsMovement::trim()
|
||||||
{
|
{
|
||||||
ContextWriter writer(m_reader, 1000);
|
ContextWriter writer(m_reader, 1000);
|
||||||
@ -296,7 +350,7 @@ void PixelsMovement::copyMask()
|
|||||||
hideDocumentMask();
|
hideDocumentMask();
|
||||||
}
|
}
|
||||||
|
|
||||||
void PixelsMovement::catchImage(const gfx::Point& pos, HandleType handle)
|
void PixelsMovement::catchImage(const gfx::PointF& pos, HandleType handle)
|
||||||
{
|
{
|
||||||
ASSERT(handle != NoHandle);
|
ASSERT(handle != NoHandle);
|
||||||
|
|
||||||
@ -305,7 +359,7 @@ void PixelsMovement::catchImage(const gfx::Point& pos, HandleType handle)
|
|||||||
m_handle = handle;
|
m_handle = handle;
|
||||||
}
|
}
|
||||||
|
|
||||||
void PixelsMovement::catchImageAgain(const gfx::Point& pos, HandleType handle)
|
void PixelsMovement::catchImageAgain(const gfx::PointF& pos, HandleType handle)
|
||||||
{
|
{
|
||||||
// Create a new Transaction to move the pixels to other position
|
// Create a new Transaction to move the pixels to other position
|
||||||
m_initialData = m_currentData;
|
m_initialData = m_currentData;
|
||||||
@ -316,33 +370,33 @@ void PixelsMovement::catchImageAgain(const gfx::Point& pos, HandleType handle)
|
|||||||
hideDocumentMask();
|
hideDocumentMask();
|
||||||
}
|
}
|
||||||
|
|
||||||
void PixelsMovement::moveImage(const gfx::Point& pos, MoveModifier moveModifier)
|
void PixelsMovement::moveImage(const gfx::PointF& pos, MoveModifier moveModifier)
|
||||||
{
|
{
|
||||||
Transformation::Corners oldCorners;
|
|
||||||
m_currentData.transformBox(oldCorners);
|
|
||||||
|
|
||||||
ContextWriter writer(m_reader, 1000);
|
ContextWriter writer(m_reader, 1000);
|
||||||
gfx::RectF bounds = m_initialData.bounds();
|
gfx::RectF bounds = m_initialData.bounds();
|
||||||
bool updateBounds = false;
|
gfx::PointF abs_initial_pivot = m_initialData.pivot();
|
||||||
double dx, dy;
|
gfx::PointF abs_pivot = m_currentData.pivot();
|
||||||
|
|
||||||
dx = ((pos.x - m_catchPos.x) * cos(m_currentData.angle()) +
|
auto newTransformation = m_currentData;
|
||||||
(pos.y - m_catchPos.y) * -sin(m_currentData.angle()));
|
|
||||||
dy = ((pos.x - m_catchPos.x) * sin(m_currentData.angle()) +
|
|
||||||
(pos.y - m_catchPos.y) * cos(m_currentData.angle()));
|
|
||||||
|
|
||||||
switch (m_handle) {
|
switch (m_handle) {
|
||||||
|
|
||||||
case MovePixelsHandle:
|
case MovePixelsHandle: {
|
||||||
|
double dx = (pos.x - m_catchPos.x);
|
||||||
|
double dy = (pos.y - m_catchPos.y);
|
||||||
|
if ((moveModifier & FineControl) == 0) {
|
||||||
|
if (dx >= 0.0) { dx = std::floor(dx); } else { dx = std::ceil(dx); }
|
||||||
|
if (dy >= 0.0) { dy = std::floor(dy); } else { dy = std::ceil(dy); }
|
||||||
|
}
|
||||||
|
|
||||||
if ((moveModifier & LockAxisMovement) == LockAxisMovement) {
|
if ((moveModifier & LockAxisMovement) == LockAxisMovement) {
|
||||||
if (ABS(dx) < ABS(dy))
|
if (std::abs(dx) < std::abs(dy))
|
||||||
dx = 0.0;
|
dx = 0.0;
|
||||||
else
|
else
|
||||||
dy = 0.0;
|
dy = 0.0;
|
||||||
}
|
}
|
||||||
|
|
||||||
bounds.offset(dx, dy);
|
bounds.offset(dx, dy);
|
||||||
updateBounds = true;
|
|
||||||
|
|
||||||
if ((moveModifier & SnapToGridMovement) == SnapToGridMovement) {
|
if ((moveModifier & SnapToGridMovement) == SnapToGridMovement) {
|
||||||
// Snap the x1,y1 point to the grid.
|
// Snap the x1,y1 point to the grid.
|
||||||
@ -355,10 +409,15 @@ void PixelsMovement::moveImage(const gfx::Point& pos, MoveModifier moveModifier)
|
|||||||
|
|
||||||
// Now we calculate the difference from x1,y1 point and we can
|
// Now we calculate the difference from x1,y1 point and we can
|
||||||
// use it to adjust all coordinates (x1, y1, x2, y2).
|
// use it to adjust all coordinates (x1, y1, x2, y2).
|
||||||
gridOffset -= bounds.origin();
|
bounds.setOrigin(gridOffset);
|
||||||
bounds.offset(gridOffset);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
newTransformation.bounds(bounds);
|
||||||
|
newTransformation.pivot(abs_initial_pivot +
|
||||||
|
bounds.origin() -
|
||||||
|
m_initialData.bounds().origin());
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
case ScaleNWHandle:
|
case ScaleNWHandle:
|
||||||
case ScaleNHandle:
|
case ScaleNHandle:
|
||||||
@ -379,8 +438,7 @@ void PixelsMovement::moveImage(const gfx::Point& pos, MoveModifier moveModifier)
|
|||||||
handles[m_handle-ScaleNWHandle][1]);
|
handles[m_handle-ScaleNWHandle][1]);
|
||||||
|
|
||||||
if ((moveModifier & ScaleFromPivot) == ScaleFromPivot) {
|
if ((moveModifier & ScaleFromPivot) == ScaleFromPivot) {
|
||||||
pivot.x = m_currentData.pivot().x;
|
pivot = m_currentData.pivot();
|
||||||
pivot.y = m_currentData.pivot().y;
|
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
pivot.x = 1.0 - handle.x;
|
pivot.x = 1.0 - handle.x;
|
||||||
@ -393,26 +451,36 @@ void PixelsMovement::moveImage(const gfx::Point& pos, MoveModifier moveModifier)
|
|||||||
gfx::PointF b = bounds.point2();
|
gfx::PointF b = bounds.point2();
|
||||||
|
|
||||||
if ((moveModifier & MaintainAspectRatioMovement) == MaintainAspectRatioMovement) {
|
if ((moveModifier & MaintainAspectRatioMovement) == MaintainAspectRatioMovement) {
|
||||||
auto u = point2Vector(gfx::PointF(m_catchPos) - pivot);
|
vec2 u = to_vec2(m_catchPos - pivot);
|
||||||
auto v = point2Vector(gfx::PointF(pos) - pivot);
|
vec2 v = to_vec2(pos - pivot);
|
||||||
auto w = v.projectOn(u);
|
vec2 w = v.projectOn(u);
|
||||||
double scale = u.magnitude();
|
double scale = u.magnitude();
|
||||||
if (scale != 0.0)
|
if (scale != 0.0) {
|
||||||
scale = (std::fabs(w.angle()-u.angle()) < PI/2.0 ? 1.0: -1.0) * w.magnitude() / scale;
|
scale = (std::fabs(w.angle()-u.angle()) < PI/2.0 ? 1.0: -1.0) * w.magnitude() / scale;
|
||||||
|
}
|
||||||
else
|
else
|
||||||
scale = 1.0;
|
scale = 1.0;
|
||||||
|
|
||||||
a.x = int((a.x-pivot.x)*scale + pivot.x);
|
a.x = ((a.x-pivot.x)*scale + pivot.x);
|
||||||
a.y = int((a.y-pivot.y)*scale + pivot.y);
|
a.y = ((a.y-pivot.y)*scale + pivot.y);
|
||||||
b.x = int((b.x-pivot.x)*scale + pivot.x);
|
b.x = ((b.x-pivot.x)*scale + pivot.x);
|
||||||
b.y = int((b.y-pivot.y)*scale + pivot.y);
|
b.y = ((b.y-pivot.y)*scale + pivot.y);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
handle.x = bounds.x + bounds.w*handle.x;
|
handle.x = bounds.x + bounds.w*handle.x;
|
||||||
handle.y = bounds.y + bounds.h*handle.y;
|
handle.y = bounds.y + bounds.h*handle.y;
|
||||||
|
|
||||||
|
double z = m_currentData.angle();
|
||||||
double w = (handle.x-pivot.x);
|
double w = (handle.x-pivot.x);
|
||||||
double h = (handle.y-pivot.y);
|
double h = (handle.y-pivot.y);
|
||||||
|
double dx = ((pos.x - m_catchPos.x) * std::cos(z) +
|
||||||
|
(pos.y - m_catchPos.y) * -std::sin(z));
|
||||||
|
double dy = ((pos.x - m_catchPos.x) * std::sin(z) +
|
||||||
|
(pos.y - m_catchPos.y) * std::cos(z));
|
||||||
|
if ((moveModifier & FineControl) == 0) {
|
||||||
|
if (dx >= 0.0) { dx = std::floor(dx); } else { dx = std::ceil(dx); }
|
||||||
|
if (dy >= 0.0) { dy = std::floor(dy); } else { dy = std::ceil(dy); }
|
||||||
|
}
|
||||||
|
|
||||||
if (m_handle == ScaleNHandle || m_handle == ScaleSHandle) {
|
if (m_handle == ScaleNHandle || m_handle == ScaleSHandle) {
|
||||||
dx = 0.0;
|
dx = 0.0;
|
||||||
@ -423,10 +491,10 @@ void PixelsMovement::moveImage(const gfx::Point& pos, MoveModifier moveModifier)
|
|||||||
h = 1.0;
|
h = 1.0;
|
||||||
}
|
}
|
||||||
|
|
||||||
a.x = int((a.x-pivot.x)*(1.0+dx/w) + pivot.x);
|
a.x = ((a.x-pivot.x)*(1.0+dx/w) + pivot.x);
|
||||||
a.y = int((a.y-pivot.y)*(1.0+dy/h) + pivot.y);
|
a.y = ((a.y-pivot.y)*(1.0+dy/h) + pivot.y);
|
||||||
b.x = int((b.x-pivot.x)*(1.0+dx/w) + pivot.x);
|
b.x = ((b.x-pivot.x)*(1.0+dx/w) + pivot.x);
|
||||||
b.y = int((b.y-pivot.y)*(1.0+dy/h) + pivot.y);
|
b.y = ((b.y-pivot.y)*(1.0+dy/h) + pivot.y);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Do not use "gfx::Rect(a, b)" here because if a > b we want to
|
// Do not use "gfx::Rect(a, b)" here because if a > b we want to
|
||||||
@ -437,109 +505,210 @@ void PixelsMovement::moveImage(const gfx::Point& pos, MoveModifier moveModifier)
|
|||||||
bounds.w = b.x - a.x;
|
bounds.w = b.x - a.x;
|
||||||
bounds.h = b.y - a.y;
|
bounds.h = b.y - a.y;
|
||||||
|
|
||||||
updateBounds = true;
|
newTransformation.bounds(bounds);
|
||||||
|
m_adjustPivot = true;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
case RotateNWHandle:
|
case RotateNWHandle:
|
||||||
case RotateNHandle:
|
|
||||||
case RotateNEHandle:
|
case RotateNEHandle:
|
||||||
case RotateWHandle:
|
|
||||||
case RotateEHandle:
|
|
||||||
case RotateSWHandle:
|
case RotateSWHandle:
|
||||||
case RotateSHandle:
|
case RotateSEHandle: {
|
||||||
case RotateSEHandle:
|
double da = (std::atan2((double)(-pos.y + abs_pivot.y),
|
||||||
{
|
(double)(+pos.x - abs_pivot.x)) -
|
||||||
gfx::PointF abs_initial_pivot = m_initialData.pivot();
|
std::atan2((double)(-m_catchPos.y + abs_initial_pivot.y),
|
||||||
gfx::PointF abs_pivot = m_currentData.pivot();
|
(double)(+m_catchPos.x - abs_initial_pivot.x)));
|
||||||
|
double newAngle = m_initialData.angle() + da;
|
||||||
|
newAngle = base::fmod_radians(newAngle);
|
||||||
|
|
||||||
double newAngle =
|
// Is the "angle snap" is activated, we've to snap the angle
|
||||||
m_initialData.angle()
|
// to common (pixel art) angles.
|
||||||
+ atan2((double)(-pos.y + abs_pivot.y),
|
if ((moveModifier & AngleSnapMovement) == AngleSnapMovement) {
|
||||||
(double)(+pos.x - abs_pivot.x))
|
// TODO make this configurable
|
||||||
- atan2((double)(-m_catchPos.y + abs_initial_pivot.y),
|
static const double keyAngles[] = {
|
||||||
(double)(+m_catchPos.x - abs_initial_pivot.x));
|
0.0, 26.565, 45.0, 63.435, 90.0, 116.565, 135.0, 153.435, 180.0,
|
||||||
|
180.0, -153.435, -135.0, -116, -90.0, -63.435, -45.0, -26.565
|
||||||
|
};
|
||||||
|
|
||||||
newAngle = base::fmod_radians(newAngle);
|
double newAngleDegrees = 180.0 * newAngle / PI;
|
||||||
|
|
||||||
// Is the "angle snap" is activated, we've to snap the angle
|
int closest = 0;
|
||||||
// to common (pixel art) angles.
|
int last = sizeof(keyAngles) / sizeof(keyAngles[0]) - 1;
|
||||||
if ((moveModifier & AngleSnapMovement) == AngleSnapMovement) {
|
for (int i=0; i<=last; ++i) {
|
||||||
// TODO make this configurable
|
if (std::fabs(newAngleDegrees-keyAngles[closest]) >
|
||||||
static const double keyAngles[] = {
|
std::fabs(newAngleDegrees-keyAngles[i]))
|
||||||
0.0, 26.565, 45.0, 63.435, 90.0, 116.565, 135.0, 153.435, 180.0,
|
closest = i;
|
||||||
180.0, -153.435, -135.0, -116, -90.0, -63.435, -45.0, -26.565
|
|
||||||
};
|
|
||||||
|
|
||||||
double newAngleDegrees = 180.0 * newAngle / PI;
|
|
||||||
|
|
||||||
int closest = 0;
|
|
||||||
int last = sizeof(keyAngles) / sizeof(keyAngles[0]) - 1;
|
|
||||||
for (int i=0; i<=last; ++i) {
|
|
||||||
if (std::fabs(newAngleDegrees-keyAngles[closest]) >
|
|
||||||
std::fabs(newAngleDegrees-keyAngles[i]))
|
|
||||||
closest = i;
|
|
||||||
}
|
|
||||||
|
|
||||||
newAngle = PI * keyAngles[closest] / 180.0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
m_currentData.angle(newAngle);
|
newAngle = PI * keyAngles[closest] / 180.0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
newTransformation.angle(newAngle);
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
case PivotHandle:
|
case SkewNHandle:
|
||||||
{
|
case SkewSHandle:
|
||||||
// Calculate the new position of the pivot
|
case SkewWHandle:
|
||||||
gfx::PointF newPivot(m_initialData.pivot().x + (pos.x - m_catchPos.x),
|
case SkewEHandle: {
|
||||||
m_initialData.pivot().y + (pos.y - m_catchPos.y));
|
// u
|
||||||
|
// ------>
|
||||||
|
//
|
||||||
|
// A --- B |
|
||||||
|
// | | | v
|
||||||
|
// | | |
|
||||||
|
// C --- D v
|
||||||
|
auto corners = m_initialData.transformedCorners();
|
||||||
|
auto A = corners[Transformation::Corners::LEFT_TOP];
|
||||||
|
auto B = corners[Transformation::Corners::RIGHT_TOP];
|
||||||
|
auto C = corners[Transformation::Corners::LEFT_BOTTOM];
|
||||||
|
auto D = corners[Transformation::Corners::RIGHT_BOTTOM];
|
||||||
|
|
||||||
m_currentData = m_initialData;
|
// Pivot in pixels
|
||||||
m_currentData.displacePivotTo(newPivot);
|
gfx::PointF pivotPoint = m_currentData.pivot();
|
||||||
|
|
||||||
|
// Pivot in [0.0, 1.0] range
|
||||||
|
gfx::PointF pivot((pivotPoint.x - bounds.x) / bounds.w,
|
||||||
|
(pivotPoint.y - bounds.y) / bounds.h);
|
||||||
|
|
||||||
|
// Vector from AB (or CD), and AC (or BD)
|
||||||
|
vec2 u = to_vec2(B - A);
|
||||||
|
vec2 v = to_vec2(C - A);
|
||||||
|
|
||||||
|
// Move PQ and RS side by a delta value projected on u vector
|
||||||
|
vec2 delta = to_vec2(pos - m_catchPos);
|
||||||
|
switch (m_handle) {
|
||||||
|
case SkewNHandle:
|
||||||
|
delta = delta.projectOn(u);
|
||||||
|
A.x += delta.x;
|
||||||
|
A.y += delta.y;
|
||||||
|
B.x += delta.x;
|
||||||
|
B.y += delta.y;
|
||||||
|
break;
|
||||||
|
case SkewSHandle:
|
||||||
|
delta = delta.projectOn(u);
|
||||||
|
C.x += delta.x;
|
||||||
|
C.y += delta.y;
|
||||||
|
D.x += delta.x;
|
||||||
|
D.y += delta.y;
|
||||||
|
break;
|
||||||
|
case SkewWHandle: {
|
||||||
|
delta = delta.projectOn(v);
|
||||||
|
A.x += delta.x;
|
||||||
|
A.y += delta.y;
|
||||||
|
C.x += delta.x;
|
||||||
|
C.y += delta.y;
|
||||||
|
|
||||||
|
vec2 toPivot = to_vec2(pivotPoint - (A*(1.0-pivot.y) + C*pivot.y));
|
||||||
|
// TODO avoid division by zero (which happens when the pivot
|
||||||
|
// is in the exact same X pos as the west handle edge)
|
||||||
|
vec2 toOtherSide = toPivot / pivot.x;
|
||||||
|
B = A + to_point(toOtherSide);
|
||||||
|
D = C + to_point(toOtherSide);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case SkewEHandle: {
|
||||||
|
delta = delta.projectOn(v);
|
||||||
|
B.x += delta.x;
|
||||||
|
B.y += delta.y;
|
||||||
|
D.x += delta.x;
|
||||||
|
D.y += delta.y;
|
||||||
|
|
||||||
|
vec2 toPivot = to_vec2(pivotPoint - (B*(1.0-pivot.y) + D*pivot.y));
|
||||||
|
// TODO avoid division by zero (which happens when the pivot
|
||||||
|
// is in the exact same X pos as the east handle edge)
|
||||||
|
vec2 toOtherSide = toPivot / (1.0-pivot.x);
|
||||||
|
A = B + to_point(toOtherSide);
|
||||||
|
C = D + to_point(toOtherSide);
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// t0 will be a transformation without skew, so we can compare
|
||||||
|
// the angle between vector PR with skew and without skew.
|
||||||
|
auto t0 = m_initialData;
|
||||||
|
t0.skew(0.0);
|
||||||
|
auto corners0 = t0.transformedCorners();
|
||||||
|
auto A0 = corners0[Transformation::Corners::LEFT_TOP];
|
||||||
|
auto B0 = corners0[Transformation::Corners::RIGHT_TOP];
|
||||||
|
auto C0 = corners0[Transformation::Corners::LEFT_BOTTOM];
|
||||||
|
|
||||||
|
// A0 ------- B
|
||||||
|
// /| /
|
||||||
|
// / ACp / <- pivot position
|
||||||
|
// / | /
|
||||||
|
// C -C0----- D
|
||||||
|
vec2 AC0 = to_vec2(C0 - A0);
|
||||||
|
auto ACp = A0*(1.0-pivot.y) + C0*pivot.y;
|
||||||
|
vec2 AC;
|
||||||
|
switch (m_handle) {
|
||||||
|
case SkewNHandle: AC = to_vec2(ACp - A); break;
|
||||||
|
case SkewSHandle: AC = to_vec2(C - ACp); break;
|
||||||
|
case SkewWHandle:
|
||||||
|
case SkewEHandle: {
|
||||||
|
vec2 AB = to_vec2(B - A);
|
||||||
|
bounds.w = AB.magnitude();
|
||||||
|
bounds.x = pivotPoint.x - bounds.w*pivot.x;
|
||||||
|
|
||||||
|
// New rotation angle is the angle between AB points
|
||||||
|
newTransformation.angle(-AB.angle());
|
||||||
|
|
||||||
|
// New skew angle is the angle between AC0 (vector from A to
|
||||||
|
// B rotated 45 degrees, like an AC vector without skew) and
|
||||||
|
// the current to AC vector.
|
||||||
|
//
|
||||||
|
// B
|
||||||
|
// / |
|
||||||
|
// / |
|
||||||
|
// / |
|
||||||
|
// A |
|
||||||
|
// | \ D
|
||||||
|
// | \ /
|
||||||
|
// | / <- AC0=AB rotated 45 degrees, if pivot is here
|
||||||
|
// | /
|
||||||
|
// C
|
||||||
|
auto ABp = A*(1.0-pivot.x) + B*pivot.x;
|
||||||
|
AC0 = vec2(ABp.y - B.y, B.x - ABp.x);
|
||||||
|
AC = to_vec2(C - A);
|
||||||
|
|
||||||
|
bounds.h = AC.projectOn(AC0).magnitude();
|
||||||
|
bounds.y = pivotPoint.y - bounds.h*pivot.y;
|
||||||
|
newTransformation.bounds(bounds);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calculate angle between AC and AC0
|
||||||
|
double newSkew = std::atan2(AC.x*AC0.y - AC.y*AC0.x, AC * AC0);
|
||||||
|
newTransformation.skew(newSkew);
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case PivotHandle: {
|
||||||
|
// Calculate the new position of the pivot
|
||||||
|
gfx::PointF newPivot = m_initialData.pivot() + pos - m_catchPos;
|
||||||
|
newTransformation = m_initialData;
|
||||||
|
newTransformation.displacePivotTo(newPivot);
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (updateBounds) {
|
setTransformationBase(newTransformation);
|
||||||
m_currentData.bounds(bounds);
|
|
||||||
m_adjustPivot = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
redrawExtraImage();
|
|
||||||
|
|
||||||
m_document->setTransformation(m_currentData);
|
|
||||||
|
|
||||||
// Get the new transformed corners
|
|
||||||
Transformation::Corners newCorners;
|
|
||||||
m_currentData.transformBox(newCorners);
|
|
||||||
|
|
||||||
// Create a union of all corners, and that will be the bounds to
|
|
||||||
// redraw of the sprite.
|
|
||||||
gfx::Rect fullBounds;
|
|
||||||
for (int i=0; i<Transformation::Corners::NUM_OF_CORNERS; ++i) {
|
|
||||||
fullBounds = fullBounds.createUnion(gfx::Rect((int)oldCorners[i].x, (int)oldCorners[i].y, 1, 1));
|
|
||||||
fullBounds = fullBounds.createUnion(gfx::Rect((int)newCorners[i].x, (int)newCorners[i].y, 1, 1));
|
|
||||||
}
|
|
||||||
|
|
||||||
// If "fullBounds" is empty is because the cel was not moved
|
|
||||||
if (!fullBounds.isEmpty()) {
|
|
||||||
// Notify the modified region.
|
|
||||||
m_document->notifySpritePixelsModified(
|
|
||||||
m_site.sprite(),
|
|
||||||
gfx::Region(fullBounds),
|
|
||||||
m_site.frame());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void PixelsMovement::getDraggedImageCopy(std::unique_ptr<Image>& outputImage,
|
void PixelsMovement::getDraggedImageCopy(std::unique_ptr<Image>& outputImage,
|
||||||
std::unique_ptr<Mask>& outputMask)
|
std::unique_ptr<Mask>& outputMask)
|
||||||
{
|
{
|
||||||
gfx::Rect bounds = m_currentData.transformedBounds();
|
gfx::Rect bounds = m_currentData.transformedBounds();
|
||||||
|
if (bounds.isEmpty())
|
||||||
|
return;
|
||||||
|
|
||||||
std::unique_ptr<Image> image(
|
std::unique_ptr<Image> image(
|
||||||
Image::create(
|
Image::create(
|
||||||
m_site.sprite()->pixelFormat(), bounds.w, bounds.h));
|
m_site.sprite()->pixelFormat(), bounds.w, bounds.h));
|
||||||
|
|
||||||
drawImage(m_currentData, image.get(), bounds.origin(), false);
|
drawImage(m_currentData, image.get(),
|
||||||
|
gfx::PointF(bounds.origin()), false);
|
||||||
|
|
||||||
// Draw mask without shrinking it, so the mask size is equal to the
|
// Draw mask without shrinking it, so the mask size is equal to the
|
||||||
// "image" render.
|
// "image" render.
|
||||||
@ -623,8 +792,11 @@ void PixelsMovement::stampImage(bool finalStamp)
|
|||||||
|
|
||||||
void PixelsMovement::stampExtraCelImage()
|
void PixelsMovement::stampExtraCelImage()
|
||||||
{
|
{
|
||||||
const Cel* cel = m_extraCel->cel();
|
|
||||||
const Image* image = m_extraCel->image();
|
const Image* image = m_extraCel->image();
|
||||||
|
if (!image)
|
||||||
|
return;
|
||||||
|
|
||||||
|
const Cel* cel = m_extraCel->cel();
|
||||||
|
|
||||||
// Expand the canvas to paste the image in the fully visible
|
// Expand the canvas to paste the image in the fully visible
|
||||||
// portion of sprite.
|
// portion of sprite.
|
||||||
@ -664,27 +836,10 @@ void PixelsMovement::dropImageTemporarily()
|
|||||||
// Displace the pivot to the new site:
|
// Displace the pivot to the new site:
|
||||||
if (m_adjustPivot) {
|
if (m_adjustPivot) {
|
||||||
m_adjustPivot = false;
|
m_adjustPivot = false;
|
||||||
|
adjustPivot();
|
||||||
|
|
||||||
// Get the a factor for the X/Y position of the initial pivot
|
if (m_delegate)
|
||||||
// position inside the initial non-rotated bounds.
|
m_delegate->onPivotChange();
|
||||||
gfx::PointF pivotPosFactor(m_initialData.pivot() - m_initialData.bounds().origin());
|
|
||||||
pivotPosFactor.x /= m_initialData.bounds().w;
|
|
||||||
pivotPosFactor.y /= m_initialData.bounds().h;
|
|
||||||
|
|
||||||
// Get the current transformed bounds.
|
|
||||||
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.
|
|
||||||
base::Vector2d<double> 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::PointF(newPivot.x, newPivot.y));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
redrawCurrentMask();
|
redrawCurrentMask();
|
||||||
@ -694,6 +849,29 @@ void PixelsMovement::dropImageTemporarily()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void PixelsMovement::adjustPivot()
|
||||||
|
{
|
||||||
|
// Get the a factor for the X/Y position of the initial pivot
|
||||||
|
// position inside the initial non-rotated bounds.
|
||||||
|
gfx::PointF pivotPosFactor(m_initialData.pivot() - m_initialData.bounds().origin());
|
||||||
|
pivotPosFactor.x /= m_initialData.bounds().w;
|
||||||
|
pivotPosFactor.y /= m_initialData.bounds().h;
|
||||||
|
|
||||||
|
// Get the current transformed bounds.
|
||||||
|
auto corners = m_currentData.transformedCorners();
|
||||||
|
|
||||||
|
// 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.
|
||||||
|
vec2 newPivot(corners.leftTop().x,
|
||||||
|
corners.leftTop().y);
|
||||||
|
newPivot += pivotPosFactor.x * to_vec2(corners.rightTop() - corners.leftTop());
|
||||||
|
newPivot += pivotPosFactor.y * to_vec2(corners.leftBottom() - corners.leftTop());
|
||||||
|
|
||||||
|
m_currentData.displacePivotTo(gfx::PointF(newPivot.x, newPivot.y));
|
||||||
|
}
|
||||||
|
|
||||||
void PixelsMovement::dropImage()
|
void PixelsMovement::dropImage()
|
||||||
{
|
{
|
||||||
m_isDragging = false;
|
m_isDragging = false;
|
||||||
@ -780,28 +958,35 @@ void PixelsMovement::redrawExtraImage(Transformation* transformation)
|
|||||||
Cel* cel = m_site.cel();
|
Cel* cel = m_site.cel();
|
||||||
if (cel) opacity = MUL_UN8(opacity, cel->opacity(), t);
|
if (cel) opacity = MUL_UN8(opacity, cel->opacity(), t);
|
||||||
|
|
||||||
gfx::Rect bounds = transformation->transformedBounds();
|
|
||||||
|
|
||||||
if (!m_extraCel)
|
if (!m_extraCel)
|
||||||
m_extraCel.reset(new ExtraCel);
|
m_extraCel.reset(new ExtraCel);
|
||||||
|
|
||||||
m_extraCel->create(
|
gfx::Rect bounds = transformation->transformedBounds();
|
||||||
m_site.tilemapMode(),
|
if (!bounds.isEmpty()) {
|
||||||
m_document->sprite(),
|
m_extraCel->create(
|
||||||
bounds,
|
m_site.tilemapMode(),
|
||||||
(m_site.tilemapMode() == TilemapMode::Tiles ? m_site.grid().tileToCanvas(bounds).size():
|
m_document->sprite(),
|
||||||
bounds.size()),
|
bounds,
|
||||||
m_site.frame(),
|
(m_site.tilemapMode() == TilemapMode::Tiles ? m_site.grid().tileToCanvas(bounds).size():
|
||||||
opacity);
|
bounds.size()),
|
||||||
m_extraCel->setType(render::ExtraType::PATCH);
|
m_site.frame(),
|
||||||
m_extraCel->setBlendMode(m_site.layer()->isImage() ?
|
opacity);
|
||||||
static_cast<LayerImage*>(m_site.layer())->blendMode():
|
m_extraCel->setType(render::ExtraType::PATCH);
|
||||||
BlendMode::NORMAL);
|
m_extraCel->setBlendMode(m_site.layer()->isImage() ?
|
||||||
|
static_cast<LayerImage*>(m_site.layer())->blendMode():
|
||||||
|
BlendMode::NORMAL);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
m_extraCel->reset();
|
||||||
|
|
||||||
m_document->setExtraCel(m_extraCel);
|
m_document->setExtraCel(m_extraCel);
|
||||||
|
|
||||||
// Draw the transformed pixels in the extra-cel which is the chunk
|
if (m_extraCel->image()) {
|
||||||
// of pixels that the user is moving.
|
// Draw the transformed pixels in the extra-cel which is the chunk
|
||||||
drawImage(*transformation, m_extraCel->image(), bounds.origin(), true);
|
// of pixels that the user is moving.
|
||||||
|
drawImage(*transformation, m_extraCel->image(),
|
||||||
|
gfx::PointF(bounds.origin()), true);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void PixelsMovement::redrawCurrentMask()
|
void PixelsMovement::redrawCurrentMask()
|
||||||
@ -811,13 +996,12 @@ void PixelsMovement::redrawCurrentMask()
|
|||||||
|
|
||||||
void PixelsMovement::drawImage(
|
void PixelsMovement::drawImage(
|
||||||
const Transformation& transformation,
|
const Transformation& transformation,
|
||||||
doc::Image* dst, const gfx::Point& pt,
|
doc::Image* dst, const gfx::PointF& pt,
|
||||||
const bool renderOriginalLayer)
|
const bool renderOriginalLayer)
|
||||||
{
|
{
|
||||||
ASSERT(dst);
|
ASSERT(dst);
|
||||||
|
|
||||||
Transformation::Corners corners;
|
auto corners = transformation.transformedCorners();
|
||||||
transformation.transformBox(corners);
|
|
||||||
gfx::Rect bounds = corners.bounds();
|
gfx::Rect bounds = corners.bounds();
|
||||||
|
|
||||||
dst->setMaskColor(m_site.sprite()->transparentColor());
|
dst->setMaskColor(m_site.sprite()->transparentColor());
|
||||||
@ -854,10 +1038,14 @@ void PixelsMovement::drawImage(
|
|||||||
|
|
||||||
void PixelsMovement::drawMask(doc::Mask* mask, bool shrink)
|
void PixelsMovement::drawMask(doc::Mask* mask, bool shrink)
|
||||||
{
|
{
|
||||||
Transformation::Corners corners;
|
auto corners = m_currentData.transformedCorners();
|
||||||
m_currentData.transformBox(corners);
|
|
||||||
gfx::Rect bounds = corners.bounds();
|
gfx::Rect bounds = corners.bounds();
|
||||||
|
|
||||||
|
if (bounds.isEmpty()) {
|
||||||
|
mask->clear();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
mask->replace(bounds);
|
mask->replace(bounds);
|
||||||
if (shrink)
|
if (shrink)
|
||||||
mask->freeze();
|
mask->freeze();
|
||||||
@ -866,7 +1054,8 @@ void PixelsMovement::drawMask(doc::Mask* mask, bool shrink)
|
|||||||
mask->bitmap(),
|
mask->bitmap(),
|
||||||
m_initialMask->bitmap(),
|
m_initialMask->bitmap(),
|
||||||
nullptr,
|
nullptr,
|
||||||
corners, bounds.origin());
|
corners,
|
||||||
|
gfx::PointF(bounds.origin()));
|
||||||
if (shrink)
|
if (shrink)
|
||||||
mask->unfreeze();
|
mask->unfreeze();
|
||||||
}
|
}
|
||||||
@ -875,7 +1064,7 @@ void PixelsMovement::drawParallelogram(
|
|||||||
const Transformation& transformation,
|
const Transformation& transformation,
|
||||||
doc::Image* dst, const doc::Image* src, const doc::Mask* mask,
|
doc::Image* dst, const doc::Image* src, const doc::Mask* mask,
|
||||||
const Transformation::Corners& corners,
|
const Transformation::Corners& corners,
|
||||||
const gfx::Point& leftTop)
|
const gfx::PointF& leftTop)
|
||||||
{
|
{
|
||||||
tools::RotationAlgorithm rotAlgo = Preferences::instance().selection.rotationAlgorithm();
|
tools::RotationAlgorithm rotAlgo = Preferences::instance().selection.rotationAlgorithm();
|
||||||
|
|
||||||
@ -943,6 +1132,9 @@ void PixelsMovement::onPivotChange()
|
|||||||
{
|
{
|
||||||
set_pivot_from_preferences(m_currentData);
|
set_pivot_from_preferences(m_currentData);
|
||||||
onRotationAlgorithmChange();
|
onRotationAlgorithmChange();
|
||||||
|
|
||||||
|
if (m_delegate)
|
||||||
|
m_delegate->onPivotChange();
|
||||||
}
|
}
|
||||||
|
|
||||||
void PixelsMovement::onRotationAlgorithmChange()
|
void PixelsMovement::onRotationAlgorithmChange()
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
// Aseprite
|
// Aseprite
|
||||||
// Copyright (C) 2019 Igara Studio S.A.
|
// Copyright (C) 2019-2020 Igara Studio S.A.
|
||||||
// Copyright (C) 2001-2018 David Capello
|
// Copyright (C) 2001-2018 David Capello
|
||||||
//
|
//
|
||||||
// This program is distributed under the terms of
|
// This program is distributed under the terms of
|
||||||
@ -36,6 +36,12 @@ namespace app {
|
|||||||
class SetMask;
|
class SetMask;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class PixelsMovementDelegate {
|
||||||
|
public:
|
||||||
|
virtual ~PixelsMovementDelegate() { }
|
||||||
|
virtual void onPivotChange() = 0;
|
||||||
|
};
|
||||||
|
|
||||||
// Helper class to move pixels interactively and control undo history
|
// Helper class to move pixels interactively and control undo history
|
||||||
// correctly. The extra cel of the sprite is temporally used to show
|
// correctly. The extra cel of the sprite is temporally used to show
|
||||||
// feedback, drag, and drop the specified image in the constructor
|
// feedback, drag, and drop the specified image in the constructor
|
||||||
@ -50,6 +56,7 @@ namespace app {
|
|||||||
MaintainAspectRatioMovement = 8,
|
MaintainAspectRatioMovement = 8,
|
||||||
LockAxisMovement = 16,
|
LockAxisMovement = 16,
|
||||||
ScaleFromPivot = 32,
|
ScaleFromPivot = 32,
|
||||||
|
FineControl = 64,
|
||||||
};
|
};
|
||||||
|
|
||||||
enum CommitChangesOption {
|
enum CommitChangesOption {
|
||||||
@ -71,17 +78,18 @@ namespace app {
|
|||||||
HandleType handle() const { return m_handle; }
|
HandleType handle() const { return m_handle; }
|
||||||
bool canHandleFrameChange() const { return m_canHandleFrameChange; }
|
bool canHandleFrameChange() const { return m_canHandleFrameChange; }
|
||||||
|
|
||||||
|
void setDelegate(PixelsMovementDelegate* delegate);
|
||||||
void setFastMode(const bool fastMode);
|
void setFastMode(const bool fastMode);
|
||||||
|
|
||||||
void trim();
|
void trim();
|
||||||
void cutMask();
|
void cutMask();
|
||||||
void copyMask();
|
void copyMask();
|
||||||
void catchImage(const gfx::Point& pos, HandleType handle);
|
void catchImage(const gfx::PointF& pos, HandleType handle);
|
||||||
void catchImageAgain(const gfx::Point& pos, HandleType handle);
|
void catchImageAgain(const gfx::PointF& pos, HandleType handle);
|
||||||
|
|
||||||
// Moves the image to the new position (relative to the start
|
// Moves the image to the new position (relative to the start
|
||||||
// position given in the ctor).
|
// position given in the ctor).
|
||||||
void moveImage(const gfx::Point& pos, MoveModifier moveModifier);
|
void moveImage(const gfx::PointF& pos, MoveModifier moveModifier);
|
||||||
|
|
||||||
// Returns a copy of the current image being dragged with the
|
// Returns a copy of the current image being dragged with the
|
||||||
// current transformation.
|
// current transformation.
|
||||||
@ -118,8 +126,11 @@ namespace app {
|
|||||||
bool gotoFrame(const doc::frame_t deltaFrame);
|
bool gotoFrame(const doc::frame_t deltaFrame);
|
||||||
|
|
||||||
const Transformation& getTransformation() const { return m_currentData; }
|
const Transformation& getTransformation() const { return m_currentData; }
|
||||||
|
void setTransformation(const Transformation& t);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
void setTransformationBase(const Transformation& t);
|
||||||
|
void adjustPivot();
|
||||||
bool editMultipleCels() const;
|
bool editMultipleCels() const;
|
||||||
void stampImage(bool finalStamp);
|
void stampImage(bool finalStamp);
|
||||||
void stampExtraCelImage();
|
void stampExtraCelImage();
|
||||||
@ -129,14 +140,14 @@ namespace app {
|
|||||||
void redrawCurrentMask();
|
void redrawCurrentMask();
|
||||||
void drawImage(
|
void drawImage(
|
||||||
const Transformation& transformation,
|
const Transformation& transformation,
|
||||||
doc::Image* dst, const gfx::Point& pos,
|
doc::Image* dst, const gfx::PointF& pos,
|
||||||
const bool renderOriginalLayer);
|
const bool renderOriginalLayer);
|
||||||
void drawMask(doc::Mask* dst, bool shrink);
|
void drawMask(doc::Mask* dst, bool shrink);
|
||||||
void drawParallelogram(
|
void drawParallelogram(
|
||||||
const Transformation& transformation,
|
const Transformation& transformation,
|
||||||
doc::Image* dst, const doc::Image* src, const doc::Mask* mask,
|
doc::Image* dst, const doc::Image* src, const doc::Mask* mask,
|
||||||
const Transformation::Corners& corners,
|
const Transformation::Corners& corners,
|
||||||
const gfx::Point& leftTop);
|
const gfx::PointF& leftTop);
|
||||||
void updateDocumentMask();
|
void updateDocumentMask();
|
||||||
void hideDocumentMask();
|
void hideDocumentMask();
|
||||||
|
|
||||||
@ -149,6 +160,7 @@ namespace app {
|
|||||||
void dumpInnerCmds();
|
void dumpInnerCmds();
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
PixelsMovementDelegate* m_delegate = nullptr;
|
||||||
const ContextReader m_reader;
|
const ContextReader m_reader;
|
||||||
Site m_site;
|
Site m_site;
|
||||||
Doc* m_document;
|
Doc* m_document;
|
||||||
@ -157,7 +169,7 @@ namespace app {
|
|||||||
bool m_adjustPivot;
|
bool m_adjustPivot;
|
||||||
HandleType m_handle;
|
HandleType m_handle;
|
||||||
doc::ImageRef m_originalImage;
|
doc::ImageRef m_originalImage;
|
||||||
gfx::Point m_catchPos;
|
gfx::PointF m_catchPos;
|
||||||
Transformation m_initialData;
|
Transformation m_initialData;
|
||||||
Transformation m_currentData;
|
Transformation m_currentData;
|
||||||
std::unique_ptr<Mask> m_initialMask, m_initialMask0;
|
std::unique_ptr<Mask> m_initialMask, m_initialMask0;
|
||||||
|
@ -51,13 +51,13 @@
|
|||||||
#include "app/util/layer_utils.h"
|
#include "app/util/layer_utils.h"
|
||||||
#include "app/util/new_image_from_mask.h"
|
#include "app/util/new_image_from_mask.h"
|
||||||
#include "app/util/readable_time.h"
|
#include "app/util/readable_time.h"
|
||||||
|
#include "base/clamp.h"
|
||||||
#include "base/pi.h"
|
#include "base/pi.h"
|
||||||
#include "doc/grid.h"
|
#include "doc/grid.h"
|
||||||
#include "doc/layer.h"
|
#include "doc/layer.h"
|
||||||
#include "doc/mask.h"
|
#include "doc/mask.h"
|
||||||
#include "doc/slice.h"
|
#include "doc/slice.h"
|
||||||
#include "doc/sprite.h"
|
#include "doc/sprite.h"
|
||||||
#include "fixmath/fixmath.h"
|
|
||||||
#include "fmt/format.h"
|
#include "fmt/format.h"
|
||||||
#include "gfx/rect.h"
|
#include "gfx/rect.h"
|
||||||
#include "os/surface.h"
|
#include "os/surface.h"
|
||||||
@ -74,17 +74,6 @@ namespace app {
|
|||||||
|
|
||||||
using namespace ui;
|
using namespace ui;
|
||||||
|
|
||||||
static CursorType rotated_size_cursors[8] = {
|
|
||||||
kSizeECursor,
|
|
||||||
kSizeNECursor,
|
|
||||||
kSizeNCursor,
|
|
||||||
kSizeNWCursor,
|
|
||||||
kSizeWCursor,
|
|
||||||
kSizeSWCursor,
|
|
||||||
kSizeSCursor,
|
|
||||||
kSizeSECursor
|
|
||||||
};
|
|
||||||
|
|
||||||
#ifdef _MSC_VER
|
#ifdef _MSC_VER
|
||||||
#pragma warning(disable:4355) // warning C4355: 'this' : used in base member initializer list
|
#pragma warning(disable:4355) // warning C4355: 'this' : used in base member initializer list
|
||||||
#endif
|
#endif
|
||||||
@ -714,7 +703,7 @@ void StandbyState::startSelectionTransformation(Editor* editor,
|
|||||||
transformSelection(editor, NULL, NoHandle);
|
transformSelection(editor, NULL, NoHandle);
|
||||||
|
|
||||||
if (MovingPixelsState* movingPixels = dynamic_cast<MovingPixelsState*>(editor->getState().get())) {
|
if (MovingPixelsState* movingPixels = dynamic_cast<MovingPixelsState*>(editor->getState().get())) {
|
||||||
movingPixels->translate(move);
|
movingPixels->translate(gfx::PointF(move));
|
||||||
if (std::fabs(angle) > 1e-5)
|
if (std::fabs(angle) > 1e-5)
|
||||||
movingPixels->rotate(angle);
|
movingPixels->rotate(angle);
|
||||||
}
|
}
|
||||||
@ -905,65 +894,77 @@ bool StandbyState::Decorator::onSetCursor(tools::Ink* ink, Editor* editor, const
|
|||||||
|
|
||||||
CursorType newCursorType = kArrowCursor;
|
CursorType newCursorType = kArrowCursor;
|
||||||
const Cursor* newCursor = nullptr;
|
const Cursor* newCursor = nullptr;
|
||||||
|
int angle = 0;
|
||||||
|
|
||||||
|
// TODO improve this for depending if we are handling corners or
|
||||||
|
// sides (e.g. from sides we can use the normal to know the
|
||||||
|
// angle/mouse cursor to show)
|
||||||
|
{
|
||||||
|
gfx::Point center = gfx::Point(
|
||||||
|
editor->editorToScreenF(transformation.transformedBounds()).center());
|
||||||
|
// Adjust the cursor depending the current transformation angle.
|
||||||
|
int dy = mouseScreenPos.y - center.y;
|
||||||
|
int dx = mouseScreenPos.x - center.x;
|
||||||
|
double a = std::atan2(-dy, dx);
|
||||||
|
a += PI * (45.0/2.0) / 180.0;
|
||||||
|
a = base::fmod_radians(a) + PI;
|
||||||
|
ASSERT(a >= 0.0 && a <= 2*PI);
|
||||||
|
angle = base::clamp<int>(std::floor(7.9 * a / (2.0*PI)), 0, 7);
|
||||||
|
}
|
||||||
|
|
||||||
switch (handle) {
|
switch (handle) {
|
||||||
case ScaleNWHandle: newCursorType = kSizeNWCursor; break;
|
case ScaleNWHandle: newCursorType = kSizeNWCursor; break;
|
||||||
case ScaleNHandle: newCursorType = kSizeNCursor; break;
|
case ScaleNHandle: newCursorType = kSizeNCursor; break;
|
||||||
case ScaleNEHandle: newCursorType = kSizeNECursor; break;
|
case ScaleNEHandle: newCursorType = kSizeNECursor; break;
|
||||||
case ScaleWHandle: newCursorType = kSizeWCursor; break;
|
case ScaleWHandle: newCursorType = kSizeWCursor; break;
|
||||||
case ScaleEHandle: newCursorType = kSizeECursor; break;
|
case ScaleEHandle: newCursorType = kSizeECursor; break;
|
||||||
case ScaleSWHandle: newCursorType = kSizeSWCursor; break;
|
case ScaleSWHandle: newCursorType = kSizeSWCursor; break;
|
||||||
case ScaleSHandle: newCursorType = kSizeSCursor; break;
|
case ScaleSHandle: newCursorType = kSizeSCursor; break;
|
||||||
case ScaleSEHandle: newCursorType = kSizeSECursor; break;
|
case ScaleSEHandle: newCursorType = kSizeSECursor; break;
|
||||||
case RotateNWHandle: newCursor = theme->cursors.rotateNw(); break;
|
case PivotHandle: newCursorType = kHandCursor; break;
|
||||||
case RotateNHandle: newCursor = theme->cursors.rotateN(); break;
|
case RotateNWHandle:
|
||||||
case RotateNEHandle: newCursor = theme->cursors.rotateNe(); break;
|
case RotateNEHandle:
|
||||||
case RotateWHandle: newCursor = theme->cursors.rotateW(); break;
|
case RotateSWHandle:
|
||||||
case RotateEHandle: newCursor = theme->cursors.rotateE(); break;
|
case RotateSEHandle:
|
||||||
case RotateSWHandle: newCursor = theme->cursors.rotateSw(); break;
|
case SkewNHandle:
|
||||||
case RotateSHandle: newCursor = theme->cursors.rotateS(); break;
|
case SkewWHandle:
|
||||||
case RotateSEHandle: newCursor = theme->cursors.rotateSe(); break;
|
case SkewEHandle:
|
||||||
case PivotHandle: newCursorType = kHandCursor; break;
|
case SkewSHandle:
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
return false;
|
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 (newCursorType >= kSizeNCursor &&
|
if (newCursorType >= kSizeNCursor &&
|
||||||
newCursorType <= kSizeNWCursor) {
|
newCursorType <= kSizeNWCursor) {
|
||||||
size_t num = sizeof(rotated_size_cursors) / sizeof(rotated_size_cursors[0]);
|
const CursorType rotated_size_cursors[8] = {
|
||||||
size_t c;
|
kSizeWCursor,
|
||||||
for (c=num-1; c>0; --c)
|
kSizeSWCursor, kSizeSCursor, kSizeSECursor,
|
||||||
if (rotated_size_cursors[c] == newCursorType)
|
kSizeECursor,
|
||||||
break;
|
kSizeNECursor, kSizeNCursor, kSizeNWCursor,
|
||||||
|
|
||||||
newCursorType = rotated_size_cursors[(c+angle) % num];
|
|
||||||
}
|
|
||||||
else if (newCursor) {
|
|
||||||
auto theme = skin::SkinTheme::instance();
|
|
||||||
const Cursor* rotated_rotate_cursors[8] = {
|
|
||||||
theme->cursors.rotateE(),
|
|
||||||
theme->cursors.rotateNe(),
|
|
||||||
theme->cursors.rotateN(),
|
|
||||||
theme->cursors.rotateNw(),
|
|
||||||
theme->cursors.rotateW(),
|
|
||||||
theme->cursors.rotateSw(),
|
|
||||||
theme->cursors.rotateS(),
|
|
||||||
theme->cursors.rotateSe()
|
|
||||||
};
|
};
|
||||||
size_t num = sizeof(rotated_rotate_cursors) / sizeof(rotated_rotate_cursors[0]);
|
newCursorType = rotated_size_cursors[angle];
|
||||||
size_t c;
|
}
|
||||||
for (c=num-1; c>0; --c)
|
else if (handle >= RotateNWHandle &&
|
||||||
if (rotated_rotate_cursors[c] == newCursor)
|
handle <= RotateSEHandle) {
|
||||||
break;
|
const Cursor* rotated_rotate_cursors[8] = {
|
||||||
|
theme->cursors.rotateW(),
|
||||||
newCursor = rotated_rotate_cursors[(c+angle) % num];
|
theme->cursors.rotateSw(), theme->cursors.rotateS(), theme->cursors.rotateSe(),
|
||||||
|
theme->cursors.rotateE(),
|
||||||
|
theme->cursors.rotateNe(), theme->cursors.rotateN(), theme->cursors.rotateNw(),
|
||||||
|
};
|
||||||
|
newCursor = rotated_rotate_cursors[angle];
|
||||||
|
newCursorType = kCustomCursor;
|
||||||
|
}
|
||||||
|
else if (handle >= SkewNHandle &&
|
||||||
|
handle <= SkewSHandle) {
|
||||||
|
const Cursor* rotated_skew_cursors[8] = {
|
||||||
|
theme->cursors.skewW(),
|
||||||
|
theme->cursors.skewSw(), theme->cursors.skewS(), theme->cursors.skewSe(),
|
||||||
|
theme->cursors.skewE(),
|
||||||
|
theme->cursors.skewNe(), theme->cursors.skewN(), theme->cursors.skewNw(),
|
||||||
|
};
|
||||||
|
newCursor = rotated_skew_cursors[angle];
|
||||||
newCursorType = kCustomCursor;
|
newCursorType = kCustomCursor;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -42,13 +42,13 @@ static struct HandlesInfo {
|
|||||||
// The exact handle type ([0] for scaling, [1] for rotating).
|
// The exact handle type ([0] for scaling, [1] for rotating).
|
||||||
HandleType handle[2];
|
HandleType handle[2];
|
||||||
} handles_info[HANDLES] = {
|
} handles_info[HANDLES] = {
|
||||||
{ 1, 2, 0 << 16, { ScaleEHandle, RotateEHandle } },
|
{ 1, 2, 0 << 16, { ScaleEHandle, SkewEHandle } },
|
||||||
{ 1, 1, 32 << 16, { ScaleNEHandle, RotateNEHandle } },
|
{ 1, 1, 32 << 16, { ScaleNEHandle, RotateNEHandle } },
|
||||||
{ 0, 1, 64 << 16, { ScaleNHandle, RotateNHandle } },
|
{ 0, 1, 64 << 16, { ScaleNHandle, SkewNHandle } },
|
||||||
{ 0, 0, 96 << 16, { ScaleNWHandle, RotateNWHandle } },
|
{ 0, 0, 96 << 16, { ScaleNWHandle, RotateNWHandle } },
|
||||||
{ 0, 3, 128 << 16, { ScaleWHandle, RotateWHandle } },
|
{ 0, 3, 128 << 16, { ScaleWHandle, SkewWHandle } },
|
||||||
{ 3, 3, 160 << 16, { ScaleSWHandle, RotateSWHandle } },
|
{ 3, 3, 160 << 16, { ScaleSWHandle, RotateSWHandle } },
|
||||||
{ 3, 2, 192 << 16, { ScaleSHandle, RotateSHandle } },
|
{ 3, 2, 192 << 16, { ScaleSHandle, SkewSHandle } },
|
||||||
{ 2, 2, 224 << 16, { ScaleSEHandle, RotateSEHandle } },
|
{ 2, 2, 224 << 16, { ScaleSEHandle, RotateSEHandle } },
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -58,8 +58,7 @@ HandleType TransformHandles::getHandleAtPoint(Editor* editor, const gfx::Point&
|
|||||||
os::Surface* gfx = theme->parts.transformationHandle()->bitmap(0);
|
os::Surface* gfx = theme->parts.transformationHandle()->bitmap(0);
|
||||||
fixmath::fixed angle = fixmath::ftofix(128.0 * transform.angle() / PI);
|
fixmath::fixed angle = fixmath::ftofix(128.0 * transform.angle() / PI);
|
||||||
|
|
||||||
Transformation::Corners corners;
|
auto corners = transform.transformedCorners();
|
||||||
transform.transformBox(corners);
|
|
||||||
|
|
||||||
std::vector<gfx::Point> screenPoints;
|
std::vector<gfx::Point> screenPoints;
|
||||||
getScreenPoints(editor, corners, screenPoints);
|
getScreenPoints(editor, corners, screenPoints);
|
||||||
@ -90,8 +89,7 @@ void TransformHandles::drawHandles(Editor* editor, ui::Graphics* g,
|
|||||||
{
|
{
|
||||||
fixmath::fixed angle = fixmath::ftofix(128.0 * transform.angle() / PI);
|
fixmath::fixed angle = fixmath::ftofix(128.0 * transform.angle() / PI);
|
||||||
|
|
||||||
Transformation::Corners corners;
|
auto corners = transform.transformedCorners();
|
||||||
transform.transformBox(corners);
|
|
||||||
|
|
||||||
std::vector<gfx::Point> screenPoints;
|
std::vector<gfx::Point> screenPoints;
|
||||||
getScreenPoints(editor, corners, screenPoints);
|
getScreenPoints(editor, corners, screenPoints);
|
||||||
@ -99,8 +97,7 @@ void TransformHandles::drawHandles(Editor* editor, ui::Graphics* g,
|
|||||||
const gfx::Point origin = editor->bounds().origin();
|
const gfx::Point origin = editor->bounds().origin();
|
||||||
|
|
||||||
#if 0 // Uncomment this if you want to see the bounds in red (only for debugging purposes)
|
#if 0 // Uncomment this if you want to see the bounds in red (only for debugging purposes)
|
||||||
// -----------------------------------------------
|
{ // Bounds
|
||||||
{
|
|
||||||
gfx::Point
|
gfx::Point
|
||||||
a(transform.bounds().origin()),
|
a(transform.bounds().origin()),
|
||||||
b(transform.bounds().point2());
|
b(transform.bounds().point2());
|
||||||
@ -112,7 +109,26 @@ void TransformHandles::drawHandles(Editor* editor, ui::Graphics* g,
|
|||||||
a = editor->editorToScreen(a) - origin;
|
a = editor->editorToScreen(a) - origin;
|
||||||
g->drawRect(gfx::rgba(255, 0, 0), gfx::Rect(a.x-2, a.y-2, 5, 5));
|
g->drawRect(gfx::rgba(255, 0, 0), gfx::Rect(a.x-2, a.y-2, 5, 5));
|
||||||
}
|
}
|
||||||
// -----------------------------------------------
|
{ // Rotated bounds
|
||||||
|
const gfx::Point& a = screenPoints[0] - origin;
|
||||||
|
const gfx::Point& b = screenPoints[1] - origin;
|
||||||
|
const gfx::Point& c = screenPoints[2] - origin;
|
||||||
|
const gfx::Point& d = screenPoints[3] - origin;
|
||||||
|
|
||||||
|
ui::Paint paint;
|
||||||
|
paint.style(ui::Paint::Stroke);
|
||||||
|
paint.antialias(true);
|
||||||
|
paint.color(gfx::rgba(255, 0, 0));
|
||||||
|
|
||||||
|
gfx::Path p;
|
||||||
|
p.moveTo(a.x, a.y);
|
||||||
|
p.lineTo(b.x, b.y);
|
||||||
|
p.lineTo(c.x, c.y);
|
||||||
|
p.lineTo(d.x, d.y);
|
||||||
|
p.close();
|
||||||
|
|
||||||
|
g->drawPath(p, paint);
|
||||||
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// Draw corner handle
|
// Draw corner handle
|
||||||
@ -141,8 +157,7 @@ void TransformHandles::invalidateHandles(Editor* editor, const Transformation& t
|
|||||||
SkinTheme* theme = SkinTheme::instance();
|
SkinTheme* theme = SkinTheme::instance();
|
||||||
fixmath::fixed angle = fixmath::ftofix(128.0 * transform.angle() / PI);
|
fixmath::fixed angle = fixmath::ftofix(128.0 * transform.angle() / PI);
|
||||||
|
|
||||||
Transformation::Corners corners;
|
auto corners = transform.transformedCorners();
|
||||||
transform.transformBox(corners);
|
|
||||||
|
|
||||||
std::vector<gfx::Point> screenPoints;
|
std::vector<gfx::Point> screenPoints;
|
||||||
getScreenPoints(editor, corners, screenPoints);
|
getScreenPoints(editor, corners, screenPoints);
|
||||||
@ -266,9 +281,9 @@ void TransformHandles::getScreenPoints(
|
|||||||
|
|
||||||
screenPoints.resize(corners.size());
|
screenPoints.resize(corners.size());
|
||||||
for (size_t c=0; c<corners.size(); ++c)
|
for (size_t c=0; c<corners.size(); ++c)
|
||||||
screenPoints[c] = editor->editorToScreen(
|
screenPoints[c] = editor->editorToScreenF(
|
||||||
gfx::Point((int)corners[c].x+main.x,
|
gfx::PointF(corners[c].x+main.x,
|
||||||
(int)corners[c].y+main.y));
|
corners[c].y+main.y));
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace app
|
} // namespace app
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
// Aseprite
|
// Aseprite
|
||||||
|
// Copyright (C) 2020 Igara Studio S.A.
|
||||||
// Copyright (C) 2018 David Capello
|
// Copyright (C) 2018 David Capello
|
||||||
//
|
//
|
||||||
// This program is distributed under the terms of
|
// This program is distributed under the terms of
|
||||||
@ -12,6 +13,7 @@
|
|||||||
|
|
||||||
#include "ui/message.h"
|
#include "ui/message.h"
|
||||||
|
|
||||||
|
#include "fmt/format.h"
|
||||||
#include "tinyexpr.h"
|
#include "tinyexpr.h"
|
||||||
|
|
||||||
#include <cmath>
|
#include <cmath>
|
||||||
@ -29,14 +31,8 @@ bool ExprEntry::onProcessMessage(ui::Message* msg)
|
|||||||
{
|
{
|
||||||
switch (msg->type()) {
|
switch (msg->type()) {
|
||||||
case ui::kFocusLeaveMessage: {
|
case ui::kFocusLeaveMessage: {
|
||||||
char buf[256];
|
std::string buf;
|
||||||
if (m_decimals == 0) {
|
onFormatExprFocusLeave(buf);
|
||||||
std::snprintf(buf, sizeof(buf), "%d", onGetTextInt());
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
std::snprintf(buf, sizeof(buf), "%.*g",
|
|
||||||
m_decimals, onGetTextDouble());
|
|
||||||
}
|
|
||||||
if (text() != buf)
|
if (text() != buf)
|
||||||
setText(buf);
|
setText(buf);
|
||||||
break;
|
break;
|
||||||
@ -71,4 +67,12 @@ double ExprEntry::onGetTextDouble() const
|
|||||||
return v;
|
return v;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ExprEntry::onFormatExprFocusLeave(std::string& buf)
|
||||||
|
{
|
||||||
|
if (m_decimals == 0)
|
||||||
|
buf = fmt::format("{}", onGetTextInt());
|
||||||
|
else
|
||||||
|
buf = fmt::format("{:.{}f}", onGetTextDouble(), m_decimals);
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace app
|
} // namespace app
|
||||||
|
@ -26,6 +26,8 @@ namespace app {
|
|||||||
int onGetTextInt() const override;
|
int onGetTextInt() const override;
|
||||||
double onGetTextDouble() const override;
|
double onGetTextDouble() const override;
|
||||||
|
|
||||||
|
virtual void onFormatExprFocusLeave(std::string& buf);
|
||||||
|
|
||||||
int m_decimals;
|
int m_decimals;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
// Aseprite
|
// Aseprite
|
||||||
// Copyright (C) 2018 Igara Studio S.A.
|
// Copyright (C) 2018-2020 Igara Studio S.A.
|
||||||
// Copyright (C) 2001-2018 David Capello
|
// Copyright (C) 2001-2018 David Capello
|
||||||
//
|
//
|
||||||
// This program is distributed under the terms of
|
// This program is distributed under the terms of
|
||||||
@ -63,6 +63,7 @@ namespace app {
|
|||||||
ScaleFromCenter = 0x00008000,
|
ScaleFromCenter = 0x00008000,
|
||||||
AngleSnapFromLastPoint = 0x00010000,
|
AngleSnapFromLastPoint = 0x00010000,
|
||||||
RotateShape = 0x00020000,
|
RotateShape = 0x00020000,
|
||||||
|
FineControl = 0x00040000,
|
||||||
};
|
};
|
||||||
|
|
||||||
enum class WheelAction {
|
enum class WheelAction {
|
||||||
@ -100,10 +101,12 @@ namespace app {
|
|||||||
|
|
||||||
class Key {
|
class Key {
|
||||||
public:
|
public:
|
||||||
Key(Command* command, const Params& params, KeyContext keyContext);
|
Key(Command* command, const Params& params,
|
||||||
Key(KeyType type, tools::Tool* tool);
|
const KeyContext keyContext);
|
||||||
explicit Key(KeyAction action);
|
Key(const KeyType type, tools::Tool* tool);
|
||||||
explicit Key(WheelAction action);
|
explicit Key(const KeyAction action,
|
||||||
|
const KeyContext keyContext);
|
||||||
|
explicit Key(const WheelAction action);
|
||||||
|
|
||||||
KeyType type() const { return m_type; }
|
KeyType type() const { return m_type; }
|
||||||
const ui::Accelerators& accels() const {
|
const ui::Accelerators& accels() const {
|
||||||
|
@ -40,26 +40,29 @@ namespace {
|
|||||||
const char* name;
|
const char* name;
|
||||||
const char* userfriendly;
|
const char* userfriendly;
|
||||||
app::KeyAction action;
|
app::KeyAction action;
|
||||||
|
app::KeyContext context;
|
||||||
} actions[] = {
|
} actions[] = {
|
||||||
{ "CopySelection" , "Copy Selection" , app::KeyAction::CopySelection },
|
{ "CopySelection" , "Copy Selection" , app::KeyAction::CopySelection, app::KeyContext::TranslatingSelection },
|
||||||
{ "SnapToGrid" , "Snap To Grid" , app::KeyAction::SnapToGrid },
|
{ "SnapToGrid" , "Snap To Grid" , app::KeyAction::SnapToGrid, app::KeyContext::TranslatingSelection },
|
||||||
{ "AngleSnap" , "Angle Snap" , app::KeyAction::AngleSnap },
|
{ "LockAxis" , "Lock Axis" , app::KeyAction::LockAxis, app::KeyContext::TranslatingSelection },
|
||||||
{ "MaintainAspectRatio" , "Maintain Aspect Ratio", app::KeyAction::MaintainAspectRatio },
|
{ "FineControl" , "Fine Translating" , app::KeyAction::FineControl , app::KeyContext::TranslatingSelection },
|
||||||
{ "ScaleFromCenter" , "Scale From Center" , app::KeyAction::ScaleFromCenter },
|
{ "MaintainAspectRatio" , "Maintain Aspect Ratio", app::KeyAction::MaintainAspectRatio, app::KeyContext::ScalingSelection },
|
||||||
{ "LockAxis" , "Lock Axis" , app::KeyAction::LockAxis },
|
{ "ScaleFromCenter" , "Scale From Center" , app::KeyAction::ScaleFromCenter, app::KeyContext::ScalingSelection },
|
||||||
{ "AddSelection" , "Add Selection" , app::KeyAction::AddSelection },
|
{ "FineControl" , "Fine Scaling" , app::KeyAction::FineControl , app::KeyContext::ScalingSelection },
|
||||||
{ "SubtractSelection" , "Subtract Selection" , app::KeyAction::SubtractSelection },
|
{ "AngleSnap" , "Angle Snap" , app::KeyAction::AngleSnap, app::KeyContext::RotatingSelection },
|
||||||
{ "IntersectSelection" , "Intersect Selection" , app::KeyAction::IntersectSelection },
|
{ "AddSelection" , "Add Selection" , app::KeyAction::AddSelection, app::KeyContext::SelectionTool },
|
||||||
{ "AutoSelectLayer" , "Auto Select Layer" , app::KeyAction::AutoSelectLayer },
|
{ "SubtractSelection" , "Subtract Selection" , app::KeyAction::SubtractSelection, app::KeyContext::SelectionTool },
|
||||||
{ "StraightLineFromLastPoint", "Straight Line from Last Point", app::KeyAction::StraightLineFromLastPoint },
|
{ "IntersectSelection" , "Intersect Selection" , app::KeyAction::IntersectSelection, app::KeyContext::SelectionTool },
|
||||||
{ "AngleSnapFromLastPoint", "Angle Snap from Last Point", app::KeyAction::AngleSnapFromLastPoint },
|
{ "AutoSelectLayer" , "Auto Select Layer" , app::KeyAction::AutoSelectLayer, app::KeyContext::MoveTool },
|
||||||
{ "MoveOrigin" , "Move Origin" , app::KeyAction::MoveOrigin },
|
{ "StraightLineFromLastPoint", "Straight Line from Last Point", app::KeyAction::StraightLineFromLastPoint, app::KeyContext::FreehandTool },
|
||||||
{ "SquareAspect" , "Square Aspect" , app::KeyAction::SquareAspect },
|
{ "AngleSnapFromLastPoint", "Angle Snap from Last Point", app::KeyAction::AngleSnapFromLastPoint, app::KeyContext::FreehandTool },
|
||||||
{ "DrawFromCenter" , "Draw From Center" , app::KeyAction::DrawFromCenter },
|
{ "MoveOrigin" , "Move Origin" , app::KeyAction::MoveOrigin, app::KeyContext::ShapeTool },
|
||||||
{ "RotateShape" , "Rotate Shape" , app::KeyAction::RotateShape },
|
{ "SquareAspect" , "Square Aspect" , app::KeyAction::SquareAspect, app::KeyContext::ShapeTool },
|
||||||
{ "LeftMouseButton" , "Trigger Left Mouse Button" , app::KeyAction::LeftMouseButton },
|
{ "DrawFromCenter" , "Draw From Center" , app::KeyAction::DrawFromCenter, app::KeyContext::ShapeTool },
|
||||||
{ "RightMouseButton" , "Trigger Right Mouse Button" , app::KeyAction::RightMouseButton },
|
{ "RotateShape" , "Rotate Shape" , app::KeyAction::RotateShape, app::KeyContext::ShapeTool },
|
||||||
{ NULL , NULL , app::KeyAction::None }
|
{ "LeftMouseButton" , "Trigger Left Mouse Button" , app::KeyAction::LeftMouseButton, app::KeyContext::Any },
|
||||||
|
{ "RightMouseButton" , "Trigger Right Mouse Button" , app::KeyAction::RightMouseButton, app::KeyContext::Any },
|
||||||
|
{ NULL , NULL , app::KeyAction::None, app::KeyContext::Any }
|
||||||
};
|
};
|
||||||
|
|
||||||
static struct {
|
static struct {
|
||||||
@ -124,9 +127,11 @@ namespace {
|
|||||||
return shortcut;
|
return shortcut;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string get_user_friendly_string_for_keyaction(app::KeyAction action) {
|
std::string get_user_friendly_string_for_keyaction(app::KeyAction action,
|
||||||
|
app::KeyContext context) {
|
||||||
for (int c=0; actions[c].name; ++c) {
|
for (int c=0; actions[c].name; ++c) {
|
||||||
if (action == actions[c].action)
|
if (action == actions[c].action &&
|
||||||
|
context == actions[c].context)
|
||||||
return actions[c].userfriendly;
|
return actions[c].userfriendly;
|
||||||
}
|
}
|
||||||
return std::string();
|
return std::string();
|
||||||
@ -203,7 +208,7 @@ using namespace ui;
|
|||||||
//////////////////////////////////////////////////////////////////////
|
//////////////////////////////////////////////////////////////////////
|
||||||
// Key
|
// Key
|
||||||
|
|
||||||
Key::Key(Command* command, const Params& params, KeyContext keyContext)
|
Key::Key(Command* command, const Params& params, const KeyContext keyContext)
|
||||||
: m_type(KeyType::Command)
|
: m_type(KeyType::Command)
|
||||||
, m_useUsers(false)
|
, m_useUsers(false)
|
||||||
, m_keycontext(keyContext)
|
, m_keycontext(keyContext)
|
||||||
@ -212,7 +217,7 @@ Key::Key(Command* command, const Params& params, KeyContext keyContext)
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
Key::Key(KeyType type, tools::Tool* tool)
|
Key::Key(const KeyType type, tools::Tool* tool)
|
||||||
: m_type(type)
|
: m_type(type)
|
||||||
, m_useUsers(false)
|
, m_useUsers(false)
|
||||||
, m_keycontext(KeyContext::Any)
|
, m_keycontext(KeyContext::Any)
|
||||||
@ -220,12 +225,17 @@ Key::Key(KeyType type, tools::Tool* tool)
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
Key::Key(KeyAction action)
|
Key::Key(const KeyAction action,
|
||||||
|
const KeyContext keyContext)
|
||||||
: m_type(KeyType::Action)
|
: m_type(KeyType::Action)
|
||||||
, m_useUsers(false)
|
, m_useUsers(false)
|
||||||
, m_keycontext(KeyContext::Any)
|
, m_keycontext(keyContext)
|
||||||
, m_action(action)
|
, m_action(action)
|
||||||
{
|
{
|
||||||
|
if (m_keycontext != KeyContext::Any)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// Automatic key context
|
||||||
switch (action) {
|
switch (action) {
|
||||||
case KeyAction::None:
|
case KeyAction::None:
|
||||||
m_keycontext = KeyContext::Any;
|
m_keycontext = KeyContext::Any;
|
||||||
@ -233,6 +243,7 @@ Key::Key(KeyAction action)
|
|||||||
case KeyAction::CopySelection:
|
case KeyAction::CopySelection:
|
||||||
case KeyAction::SnapToGrid:
|
case KeyAction::SnapToGrid:
|
||||||
case KeyAction::LockAxis:
|
case KeyAction::LockAxis:
|
||||||
|
case KeyAction::FineControl:
|
||||||
m_keycontext = KeyContext::TranslatingSelection;
|
m_keycontext = KeyContext::TranslatingSelection;
|
||||||
break;
|
break;
|
||||||
case KeyAction::AngleSnap:
|
case KeyAction::AngleSnap:
|
||||||
@ -269,7 +280,7 @@ Key::Key(KeyAction action)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Key::Key(WheelAction wheelAction)
|
Key::Key(const WheelAction wheelAction)
|
||||||
: m_type(KeyType::WheelAction)
|
: m_type(KeyType::WheelAction)
|
||||||
, m_useUsers(false)
|
, m_useUsers(false)
|
||||||
, m_keycontext(KeyContext::MouseWheel)
|
, m_keycontext(KeyContext::MouseWheel)
|
||||||
@ -395,7 +406,8 @@ std::string Key::triggerString() const
|
|||||||
return text;
|
return text;
|
||||||
}
|
}
|
||||||
case KeyType::Action:
|
case KeyType::Action:
|
||||||
return get_user_friendly_string_for_keyaction(m_action);
|
return get_user_friendly_string_for_keyaction(m_action,
|
||||||
|
m_keycontext);
|
||||||
case KeyType::WheelAction:
|
case KeyType::WheelAction:
|
||||||
return get_user_friendly_string_for_wheelaction(m_wheelAction);
|
return get_user_friendly_string_for_wheelaction(m_wheelAction);
|
||||||
}
|
}
|
||||||
@ -570,9 +582,16 @@ void KeyboardShortcuts::importFile(TiXmlElement* rootElement, KeySource source)
|
|||||||
if (action_id) {
|
if (action_id) {
|
||||||
KeyAction action = base::convert_to<KeyAction, std::string>(action_id);
|
KeyAction action = base::convert_to<KeyAction, std::string>(action_id);
|
||||||
if (action != KeyAction::None) {
|
if (action != KeyAction::None) {
|
||||||
KeyPtr key = this->action(action);
|
// Read context
|
||||||
|
KeyContext keycontext = KeyContext::Any;
|
||||||
|
const char* keycontextstr = xmlKey->Attribute("context");
|
||||||
|
if (keycontextstr)
|
||||||
|
keycontext = base::convert_to<KeyContext>(std::string(keycontextstr));
|
||||||
|
|
||||||
|
KeyPtr key = this->action(action, keycontext);
|
||||||
if (key && action_key) {
|
if (key && action_key) {
|
||||||
LOG(VERBOSE, "KEYS: Shortcut for action %s: %s\n", action_id, action_key);
|
LOG(VERBOSE, "KEYS: Shortcut for action %s/%s: %s\n", action_id,
|
||||||
|
(keycontextstr ? keycontextstr: "Any"), action_key);
|
||||||
Accelerator accel(action_key);
|
Accelerator accel(action_key);
|
||||||
|
|
||||||
if (!removed)
|
if (!removed)
|
||||||
@ -703,6 +722,9 @@ void KeyboardShortcuts::exportAccel(TiXmlElement& parent, const Key* key, const
|
|||||||
case KeyType::Action:
|
case KeyType::Action:
|
||||||
elem.SetAttribute("action",
|
elem.SetAttribute("action",
|
||||||
base::convert_to<std::string>(key->action()).c_str());
|
base::convert_to<std::string>(key->action()).c_str());
|
||||||
|
if (key->keycontext() != KeyContext::Any)
|
||||||
|
elem.SetAttribute("context",
|
||||||
|
base::convert_to<std::string>(key->keycontext()).c_str());
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case KeyType::WheelAction:
|
case KeyType::WheelAction:
|
||||||
@ -773,16 +795,18 @@ KeyPtr KeyboardShortcuts::quicktool(tools::Tool* tool)
|
|||||||
return key;
|
return key;
|
||||||
}
|
}
|
||||||
|
|
||||||
KeyPtr KeyboardShortcuts::action(KeyAction action)
|
KeyPtr KeyboardShortcuts::action(KeyAction action,
|
||||||
|
KeyContext keyContext)
|
||||||
{
|
{
|
||||||
for (KeyPtr& key : m_keys) {
|
for (KeyPtr& key : m_keys) {
|
||||||
if (key->type() == KeyType::Action &&
|
if (key->type() == KeyType::Action &&
|
||||||
key->action() == action) {
|
key->action() == action &&
|
||||||
|
key->keycontext() == keyContext) {
|
||||||
return key;
|
return key;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
KeyPtr key = std::make_shared<Key>(action);
|
KeyPtr key = std::make_shared<Key>(action, keyContext);
|
||||||
m_keys.push_back(key);
|
m_keys.push_back(key);
|
||||||
return key;
|
return key;
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
// Aseprite
|
// Aseprite
|
||||||
|
// Copyright (C) 2020 Igara Studio S.A.
|
||||||
// Copyright (C) 2001-2018 David Capello
|
// Copyright (C) 2001-2018 David Capello
|
||||||
//
|
//
|
||||||
// This program is distributed under the terms of
|
// This program is distributed under the terms of
|
||||||
@ -43,11 +44,13 @@ namespace app {
|
|||||||
void reset();
|
void reset();
|
||||||
|
|
||||||
KeyPtr command(const char* commandName,
|
KeyPtr command(const char* commandName,
|
||||||
const Params& params = Params(), KeyContext keyContext = KeyContext::Any);
|
const Params& params = Params(),
|
||||||
|
const KeyContext keyContext = KeyContext::Any);
|
||||||
KeyPtr tool(tools::Tool* tool);
|
KeyPtr tool(tools::Tool* tool);
|
||||||
KeyPtr quicktool(tools::Tool* tool);
|
KeyPtr quicktool(tools::Tool* tool);
|
||||||
KeyPtr action(KeyAction action);
|
KeyPtr action(const KeyAction action,
|
||||||
KeyPtr wheelAction(WheelAction action);
|
const KeyContext keyContext = KeyContext::Any);
|
||||||
|
KeyPtr wheelAction(const WheelAction action);
|
||||||
|
|
||||||
void disableAccel(const ui::Accelerator& accel,
|
void disableAccel(const ui::Accelerator& accel,
|
||||||
const KeyContext keyContext,
|
const KeyContext keyContext,
|
||||||
@ -88,9 +91,11 @@ namespace app {
|
|||||||
commandName, params, keyContext).get());
|
commandName, params, keyContext).get());
|
||||||
}
|
}
|
||||||
|
|
||||||
inline std::string key_tooltip(const char* str, KeyAction keyAction) {
|
inline std::string key_tooltip(const char* str,
|
||||||
|
KeyAction keyAction,
|
||||||
|
KeyContext keyContext = KeyContext::Any) {
|
||||||
return key_tooltip(
|
return key_tooltip(
|
||||||
str, KeyboardShortcuts::instance()->action(keyAction).get());
|
str, KeyboardShortcuts::instance()->action(keyAction, keyContext).get());
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace app
|
} // namespace app
|
||||||
|
@ -157,6 +157,11 @@ void Mask::invert()
|
|||||||
|
|
||||||
void Mask::replace(const gfx::Rect& bounds)
|
void Mask::replace(const gfx::Rect& bounds)
|
||||||
{
|
{
|
||||||
|
if (bounds.isEmpty()) {
|
||||||
|
clear();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
m_bounds = bounds;
|
m_bounds = bounds;
|
||||||
|
|
||||||
m_bitmap.reset(Image::create(IMAGE_BITMAP, bounds.w, bounds.h, m_buffer));
|
m_bitmap.reset(Image::create(IMAGE_BITMAP, bounds.w, bounds.h, m_buffer));
|
||||||
|
@ -423,6 +423,9 @@ bool Entry::onProcessMessage(Message* msg)
|
|||||||
return true;
|
return true;
|
||||||
|
|
||||||
case kDoubleClickMessage:
|
case kDoubleClickMessage:
|
||||||
|
if (!hasFocus())
|
||||||
|
requestFocus();
|
||||||
|
|
||||||
forwardWord();
|
forwardWord();
|
||||||
m_select = m_caret;
|
m_select = m_caret;
|
||||||
backwardWord();
|
backwardWord();
|
||||||
|
Loading…
x
Reference in New Issue
Block a user