mirror of
https://github.com/aseprite/aseprite.git
synced 2025-03-29 19:20:09 +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_auto" x="156" 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>
|
||||
<styles>
|
||||
<style id="box" />
|
||||
|
@ -590,6 +590,12 @@
|
||||
locks the movement to one axis (X or Y) -->
|
||||
<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
|
||||
keyboard shortcut you activate angle snap -->
|
||||
<key action="AngleSnap" shortcut="Shift" />
|
||||
|
@ -58,4 +58,11 @@ void ExtraCel::create(const TilemapMode tilemapMode,
|
||||
m_cel->setFrame(frame);
|
||||
}
|
||||
|
||||
void ExtraCel::reset()
|
||||
{
|
||||
m_type = render::ExtraType::NONE;
|
||||
m_image.reset();
|
||||
m_cel.reset();
|
||||
}
|
||||
|
||||
} // namespace app
|
||||
|
@ -37,6 +37,7 @@ namespace app {
|
||||
const gfx::Size& imageSize,
|
||||
const doc::frame_t frame,
|
||||
const int opacity);
|
||||
void reset();
|
||||
|
||||
render::ExtraType type() const { return m_type; }
|
||||
void setType(render::ExtraType type) { m_type = type; }
|
||||
|
@ -1,4 +1,5 @@
|
||||
// Aseprite
|
||||
// Copyright (C) 2020 Igara Studio S.A.
|
||||
// Copyright (C) 2001-2018 David Capello
|
||||
//
|
||||
// This program is distributed under the terms of
|
||||
@ -10,7 +11,6 @@
|
||||
|
||||
#include "app/transformation.h"
|
||||
|
||||
#include "fixmath/fixmath.h"
|
||||
#include "gfx/point.h"
|
||||
#include "gfx/size.h"
|
||||
|
||||
@ -22,77 +22,66 @@ using namespace gfx;
|
||||
|
||||
Transformation::Transformation()
|
||||
{
|
||||
m_bounds.x = 0;
|
||||
m_bounds.y = 0;
|
||||
m_bounds.w = 0;
|
||||
m_bounds.h = 0;
|
||||
m_angle = 0.0;
|
||||
m_pivot.x = 0;
|
||||
m_pivot.y = 0;
|
||||
}
|
||||
|
||||
Transformation::Transformation(const RectF& bounds)
|
||||
: m_bounds(bounds)
|
||||
{
|
||||
m_angle = 0.0;
|
||||
m_pivot.x = bounds.x + bounds.w/2;
|
||||
m_pivot.y = bounds.y + bounds.h/2;
|
||||
}
|
||||
|
||||
void Transformation::transformBox(Corners& corners) const
|
||||
Transformation::Corners Transformation::transformedCorners() const
|
||||
{
|
||||
Corners corners(m_bounds);
|
||||
|
||||
// TODO We could create a composed 4x4 matrix with all
|
||||
// transformation and apply the same matrix to avoid calling
|
||||
// rotatePoint/cos/sin functions 4 times, anyway, it's not
|
||||
// critical at this point.
|
||||
|
||||
corners = m_bounds;
|
||||
for (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)
|
||||
{
|
||||
// Calculate the rotated corners
|
||||
Corners corners;
|
||||
transformBox(corners);
|
||||
Corners corners = transformedCorners();
|
||||
|
||||
// Rotate-back (-angle) the position of the rotated origin (corners[0])
|
||||
// using the new pivot.
|
||||
PointF newBoundsOrigin =
|
||||
rotatePoint(corners.leftTop(),
|
||||
newPivot,
|
||||
-m_angle);
|
||||
// Rotate-back the position of the rotated origin (corners[0]) using
|
||||
// the new pivot.
|
||||
PointF pt = corners.leftTop();
|
||||
pt = rotatePoint(pt, newPivot, -m_angle, 0.0);
|
||||
pt = rotatePoint(pt, newPivot, 0.0, -m_skew);
|
||||
|
||||
// Change the new pivot.
|
||||
m_pivot = newPivot;
|
||||
m_bounds = RectF(newBoundsOrigin, m_bounds.size());
|
||||
m_bounds = RectF(pt, m_bounds.size());
|
||||
}
|
||||
|
||||
PointF Transformation::rotatePoint(
|
||||
const PointF& point,
|
||||
const PointF& pivot,
|
||||
double angle)
|
||||
const double angle,
|
||||
const double skew)
|
||||
{
|
||||
using namespace fixmath;
|
||||
|
||||
fixed fixangle = ftofix(-angle);
|
||||
fixangle = fixmul(fixangle, radtofix_r);
|
||||
|
||||
fixed cos = fixcos(fixangle);
|
||||
fixed sin = fixsin(fixangle);
|
||||
fixed dx = fixsub(ftofix(point.x), ftofix(pivot.x));
|
||||
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)))));
|
||||
double cos = std::cos(-angle);
|
||||
double sin = std::sin(-angle);
|
||||
double tan = std::tan(skew);
|
||||
double dx = point.x - pivot.x;
|
||||
double dy = point.y - pivot.y;
|
||||
dx += dy*tan;
|
||||
return PointF(pivot.x + dx*cos - dy*sin,
|
||||
pivot.y + dx*sin + dy*cos);
|
||||
}
|
||||
|
||||
RectF Transformation::transformedBounds() const
|
||||
{
|
||||
// Get transformed corners
|
||||
Corners corners;
|
||||
transformBox(corners);
|
||||
Corners corners = transformedCorners();
|
||||
|
||||
// Create a union of all corners
|
||||
RectF bounds;
|
||||
|
@ -1,4 +1,5 @@
|
||||
// Aseprite
|
||||
// Copyright (C) 2020 Igara Studio S.A.
|
||||
// Copyright (C) 2001-2016 David Capello
|
||||
//
|
||||
// This program is distributed under the terms of
|
||||
@ -29,6 +30,16 @@ public:
|
||||
};
|
||||
|
||||
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(); }
|
||||
|
||||
@ -45,18 +56,6 @@ public:
|
||||
void rightBottom(const gfx::PointF& pt) { m_corners[RIGHT_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;
|
||||
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::PointF& pivot() const { return m_pivot; }
|
||||
double angle() const { return m_angle; }
|
||||
double skew() const { return m_skew; }
|
||||
|
||||
void bounds(const gfx::RectF& bounds) { m_bounds = bounds; }
|
||||
void pivot(const gfx::PointF& pivot) { m_pivot = pivot; }
|
||||
void angle(double angle) { m_angle = angle; }
|
||||
void skew(double angle) { m_skew = angle; }
|
||||
|
||||
// Applies the transformation (rotation with angle/pivot) to the
|
||||
// current bounds (m_bounds).
|
||||
void transformBox(Corners& corners) const;
|
||||
Corners transformedCorners() const;
|
||||
|
||||
// Changes the pivot to another location, adjusting the bounds to
|
||||
// keep the current rotated-corners in the same location.
|
||||
@ -94,12 +95,14 @@ public:
|
||||
// Static helper method to rotate points.
|
||||
static gfx::PointF rotatePoint(const gfx::PointF& point,
|
||||
const gfx::PointF& pivot,
|
||||
double angle);
|
||||
const double angle,
|
||||
const double skew);
|
||||
|
||||
private:
|
||||
gfx::RectF m_bounds;
|
||||
gfx::PointF m_pivot;
|
||||
double m_angle;
|
||||
gfx::RectF m_bounds = gfx::RectF(0.0, 0.0, 0.0, 0.0);
|
||||
gfx::PointF m_pivot = gfx::PointF(0.0, 0.0);
|
||||
double m_angle = 0.0;
|
||||
double m_skew = 0.0;
|
||||
};
|
||||
|
||||
} // namespace app
|
||||
|
@ -41,6 +41,7 @@
|
||||
#include "app/ui/dithering_selector.h"
|
||||
#include "app/ui/dynamics_popup.h"
|
||||
#include "app/ui/editor/editor.h"
|
||||
#include "app/ui/expr_entry.h"
|
||||
#include "app/ui/icon_button.h"
|
||||
#include "app/ui/keyboard_shortcuts.h"
|
||||
#include "app/ui/selection_mode_field.h"
|
||||
@ -48,6 +49,7 @@
|
||||
#include "app/ui_context.h"
|
||||
#include "base/clamp.h"
|
||||
#include "base/fs.h"
|
||||
#include "base/pi.h"
|
||||
#include "base/scoped_value.h"
|
||||
#include "doc/brush.h"
|
||||
#include "doc/image.h"
|
||||
@ -55,6 +57,7 @@
|
||||
#include "doc/remap.h"
|
||||
#include "doc/selected_objects.h"
|
||||
#include "doc/slice.h"
|
||||
#include "fmt/format.h"
|
||||
#include "obs/connection.h"
|
||||
#include "os/surface.h"
|
||||
#include "os/system.h"
|
||||
@ -969,6 +972,156 @@ private:
|
||||
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
|
||||
, public DynamicsPopup::Delegate {
|
||||
public:
|
||||
@ -1529,10 +1682,13 @@ private:
|
||||
ContextBar::ContextBar(TooltipManager* tooltipManager,
|
||||
ColorBar* colorBar)
|
||||
{
|
||||
auto& pref = Preferences::instance();
|
||||
|
||||
addChild(m_selectionOptionsBox = new HBox());
|
||||
m_selectionOptionsBox->addChild(m_dropPixels = new DropPixelsField());
|
||||
m_selectionOptionsBox->addChild(m_selectionMode = new SelectionModeField);
|
||||
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_rotAlgo = new RotAlgorithmField());
|
||||
|
||||
@ -1573,7 +1729,7 @@ ContextBar::ContextBar(TooltipManager* tooltipManager,
|
||||
m_freehandBox->addChild(m_freehandAlgo = new FreehandAlgorithmField());
|
||||
|
||||
addChild(m_symmetry = new SymmetryField());
|
||||
m_symmetry->setVisible(Preferences::instance().symmetryMode.enabled());
|
||||
m_symmetry->setVisible(pref.symmetryMode.enabled());
|
||||
|
||||
addChild(m_sliceFields = new SliceFields);
|
||||
|
||||
@ -1582,7 +1738,6 @@ ContextBar::ContextBar(TooltipManager* tooltipManager,
|
||||
App::instance()->activeToolManager()->add_observer(this);
|
||||
UIContext::instance()->add_observer(this);
|
||||
|
||||
auto& pref = Preferences::instance();
|
||||
pref.symmetryMode.enabled.AfterChange.connect(
|
||||
[this]{ onSymmetryModeChange(); });
|
||||
pref.colorBar.fgColor.AfterChange.connect(
|
||||
@ -1927,6 +2082,7 @@ void ContextBar::updateForTool(tools::Tool* tool)
|
||||
m_selectionMode->setVisible(true);
|
||||
m_pivot->setVisible(true);
|
||||
m_dropPixels->setVisible(false);
|
||||
m_transformation->setVisible(false);
|
||||
m_selectBoxHelp->setVisible(false);
|
||||
|
||||
m_symmetry->setVisible(
|
||||
@ -1943,7 +2099,7 @@ void ContextBar::updateForTool(tools::Tool* tool)
|
||||
layout();
|
||||
}
|
||||
|
||||
void ContextBar::updateForMovingPixels()
|
||||
void ContextBar::updateForMovingPixels(const Transformation& t)
|
||||
{
|
||||
tools::Tool* tool = App::instance()->toolBox()->getToolById(
|
||||
tools::WellKnownTools::RectangularMarquee);
|
||||
@ -1953,6 +2109,8 @@ void ContextBar::updateForMovingPixels()
|
||||
m_dropPixels->deselectItems();
|
||||
m_dropPixels->setVisible(true);
|
||||
m_selectionMode->setVisible(false);
|
||||
m_transformation->setVisible(true);
|
||||
m_transformation->update(t);
|
||||
layout();
|
||||
}
|
||||
|
||||
|
@ -53,6 +53,7 @@ namespace app {
|
||||
class ColorBar;
|
||||
class DitheringSelector;
|
||||
class GradientTypeSelector;
|
||||
class Transformation;
|
||||
|
||||
class ContextBar : public DocObserverWidget<ui::HBox>
|
||||
, public obs::observable<ContextBarObserver>
|
||||
@ -64,7 +65,7 @@ namespace app {
|
||||
|
||||
void updateForActiveTool();
|
||||
void updateForTool(tools::Tool* tool);
|
||||
void updateForMovingPixels();
|
||||
void updateForMovingPixels(const Transformation& t);
|
||||
void updateForSelectingBox(const std::string& text);
|
||||
void updateToolLoopModifiersIndicators(tools::ToolLoopModifiers modifiers);
|
||||
void updateAutoSelectLayer(bool state);
|
||||
@ -148,6 +149,7 @@ namespace app {
|
||||
class TransparentColorField;
|
||||
class PivotField;
|
||||
class RotAlgorithmField;
|
||||
class TransformationFields;
|
||||
class DynamicsField;
|
||||
class FreehandAlgorithmField;
|
||||
class BrushPatternField;
|
||||
@ -187,6 +189,7 @@ namespace app {
|
||||
TransparentColorField* m_transparentColor;
|
||||
PivotField* m_pivot;
|
||||
RotAlgorithmField* m_rotAlgo;
|
||||
TransformationFields* m_transformation = nullptr;
|
||||
DropPixelsField* m_dropPixels;
|
||||
doc::BrushRef m_activeBrush;
|
||||
ui::Label* m_selectBoxHelp;
|
||||
|
@ -116,8 +116,8 @@ protected:
|
||||
case kKeyUpMessage:
|
||||
if (static_cast<KeyMessage*>(msg)->repeat() == 0) {
|
||||
KeyboardShortcuts* keys = KeyboardShortcuts::instance();
|
||||
KeyPtr lmb = keys->action(KeyAction::LeftMouseButton);
|
||||
KeyPtr rmb = keys->action(KeyAction::RightMouseButton);
|
||||
KeyPtr lmb = keys->action(KeyAction::LeftMouseButton, KeyContext::Any);
|
||||
KeyPtr rmb = keys->action(KeyAction::RightMouseButton, KeyContext::Any);
|
||||
|
||||
// Convert action keys into mouse messages.
|
||||
if (lmb->isPressed(msg, *keys) ||
|
||||
|
@ -681,7 +681,8 @@ void Editor::drawOneSpriteUnclippedRect(ui::Graphics* g, const gfx::Rect& sprite
|
||||
}
|
||||
|
||||
ExtraCelRef extraCel = m_document->extraCel();
|
||||
if (extraCel && extraCel->type() != render::ExtraType::NONE) {
|
||||
if (extraCel &&
|
||||
extraCel->type() != render::ExtraType::NONE) {
|
||||
m_renderEngine->setExtraImage(
|
||||
extraCel->type(),
|
||||
extraCel->cel(),
|
||||
@ -1307,7 +1308,7 @@ void Editor::flashCurrentLayer()
|
||||
return;
|
||||
|
||||
Site site = getSite();
|
||||
if (const Cel* src_cel = site.cel()) {
|
||||
if (site.cel()) {
|
||||
// Hide and destroy the extra cel used by the brush preview
|
||||
// because we'll need to use the extra cel now for the flashing
|
||||
// layer.
|
||||
@ -2514,7 +2515,7 @@ void Editor::pasteImage(const Image* image, const Mask* mask)
|
||||
void Editor::startSelectionTransformation(const gfx::Point& move, double angle)
|
||||
{
|
||||
if (auto movingPixels = dynamic_cast<MovingPixelsState*>(m_state.get())) {
|
||||
movingPixels->translate(move);
|
||||
movingPixels->translate(gfx::PointF(move));
|
||||
if (std::fabs(angle) > 1e-5)
|
||||
movingPixels->rotate(angle);
|
||||
}
|
||||
@ -2531,6 +2532,12 @@ void Editor::startFlipTransformation(doc::algorithm::FlipType 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()
|
||||
{
|
||||
m_observers.notifyScrollChanged(this);
|
||||
|
@ -60,6 +60,7 @@ namespace app {
|
||||
class EditorRender;
|
||||
class PixelsMovement;
|
||||
class Site;
|
||||
class Transformation;
|
||||
|
||||
namespace tools {
|
||||
class Ink;
|
||||
@ -242,8 +243,8 @@ namespace app {
|
||||
void pasteImage(const Image* image, const Mask* mask = nullptr);
|
||||
|
||||
void startSelectionTransformation(const gfx::Point& move, double angle);
|
||||
|
||||
void startFlipTransformation(doc::algorithm::FlipType flipType);
|
||||
void updateTransformation(const Transformation& transform);
|
||||
|
||||
// Used by EditorView to notify changes in the view's scroll
|
||||
// position.
|
||||
|
@ -1,4 +1,5 @@
|
||||
// Aseprite
|
||||
// Copyright (C) 2020 Igara Studio S.A.
|
||||
// Copyright (C) 2001-2017 David Capello
|
||||
//
|
||||
// This program is distributed under the terms of
|
||||
@ -22,10 +23,12 @@ namespace app {
|
||||
ScaleNWHandle, ScaleNHandle, ScaleNEHandle,
|
||||
ScaleWHandle, ScaleEHandle,
|
||||
ScaleSWHandle, ScaleSHandle, ScaleSEHandle,
|
||||
// One of the region's corders to rotate.
|
||||
RotateNWHandle, RotateNHandle, RotateNEHandle,
|
||||
RotateWHandle, RotateEHandle,
|
||||
RotateSWHandle, RotateSHandle, RotateSEHandle,
|
||||
// Rotate from corners
|
||||
RotateNWHandle, RotateNEHandle,
|
||||
RotateSWHandle, RotateSEHandle,
|
||||
// Skew from sides
|
||||
SkewNHandle, SkewWHandle,
|
||||
SkewEHandle, SkewSHandle,
|
||||
// Handle used to move the pivot
|
||||
PivotHandle,
|
||||
};
|
||||
|
@ -26,6 +26,7 @@
|
||||
#include "app/pref/preferences.h"
|
||||
#include "app/tools/ink.h"
|
||||
#include "app/tools/tool.h"
|
||||
#include "app/transformation.h"
|
||||
#include "app/ui/context_bar.h"
|
||||
#include "app/ui/editor/editor.h"
|
||||
#include "app/ui/editor/editor_customization_delegate.h"
|
||||
@ -63,9 +64,9 @@ MovingPixelsState::MovingPixelsState(Editor* editor, MouseMessage* msg, PixelsMo
|
||||
, m_observingEditor(false)
|
||||
, m_discarded(false)
|
||||
, 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
|
||||
// sharing the extra cel between the drawing cursor preview and the
|
||||
// pixels movement/transformation preview.
|
||||
@ -75,7 +76,7 @@ MovingPixelsState::MovingPixelsState(Editor* editor, MouseMessage* msg, PixelsMo
|
||||
|
||||
if (handle != NoHandle) {
|
||||
gfx::Point pt = editor->screenToEditor(msg->position());
|
||||
m_pixelsMovement->catchImage(pt, handle);
|
||||
m_pixelsMovement->catchImage(gfx::PointF(pt), handle);
|
||||
|
||||
editor->captureMouse();
|
||||
}
|
||||
@ -111,7 +112,7 @@ MovingPixelsState::MovingPixelsState(Editor* editor, MouseMessage* msg, PixelsMo
|
||||
m_observingEditor = true;
|
||||
|
||||
ContextBar* contextBar = App::instance()->contextBar();
|
||||
contextBar->updateForMovingPixels();
|
||||
contextBar->updateForMovingPixels(getTransformation(editor));
|
||||
contextBar->add_observer(this);
|
||||
}
|
||||
|
||||
@ -131,12 +132,12 @@ MovingPixelsState::~MovingPixelsState()
|
||||
m_editor->document()->generateMaskBoundaries();
|
||||
}
|
||||
|
||||
void MovingPixelsState::translate(const gfx::Point& delta)
|
||||
void MovingPixelsState::translate(const gfx::PointF& delta)
|
||||
{
|
||||
if (m_pixelsMovement->isDragging())
|
||||
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->dropImageTemporarily();
|
||||
m_editor->updateStatusBar();
|
||||
@ -160,6 +161,12 @@ void MovingPixelsState::shift(int dx, int dy)
|
||||
m_editor->updateStatusBar();
|
||||
}
|
||||
|
||||
void MovingPixelsState::updateTransformation(const Transformation& t)
|
||||
{
|
||||
m_pixelsMovement->setTransformation(t);
|
||||
m_editor->updateStatusBar();
|
||||
}
|
||||
|
||||
void MovingPixelsState::onEnterState(Editor* editor)
|
||||
{
|
||||
StandbyState::onEnterState(editor);
|
||||
@ -174,7 +181,7 @@ void MovingPixelsState::onEditorGotFocus(Editor* editor)
|
||||
// when we are back to an editor in MovingPixelsState. Without this
|
||||
// we would see the SelectionModeField instead which doesn't make
|
||||
// sense on MovingPixelsState).
|
||||
contextBar->updateForMovingPixels();
|
||||
contextBar->updateForMovingPixels(getTransformation(editor));
|
||||
}
|
||||
|
||||
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() ||
|
||||
tool->getInk(1)->isSelection()) {
|
||||
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());
|
||||
|
||||
ContextBar* contextBar = App::instance()->contextBar();
|
||||
contextBar->updateForMovingPixels();
|
||||
contextBar->updateForMovingPixels(getTransformation(editor));
|
||||
|
||||
// Start scroll loop
|
||||
if (editor->checkForScroll(msg) ||
|
||||
@ -307,8 +314,8 @@ bool MovingPixelsState::onMouseDown(Editor* editor, MouseMessage* msg)
|
||||
return true;
|
||||
|
||||
// Re-catch the image
|
||||
m_pixelsMovement->catchImageAgain(
|
||||
editor->screenToEditor(msg->position()), handle);
|
||||
gfx::Point pt = editor->screenToEditor(msg->position());
|
||||
m_pixelsMovement->catchImageAgain(gfx::PointF(pt), handle);
|
||||
|
||||
editor->captureMouse();
|
||||
return true;
|
||||
@ -332,7 +339,7 @@ bool MovingPixelsState::onMouseDown(Editor* editor, MouseMessage* msg)
|
||||
|
||||
// Re-catch the image
|
||||
m_pixelsMovement->catchImageAgain(
|
||||
editor->screenToEditor(msg->position()), MovePixelsHandle);
|
||||
editor->screenToEditorF(msg->position()), MovePixelsHandle);
|
||||
|
||||
editor->captureMouse();
|
||||
return true;
|
||||
@ -373,17 +380,7 @@ bool MovingPixelsState::onMouseMove(Editor* editor, MouseMessage* msg)
|
||||
gfx::Point mousePos = editor->autoScroll(msg, AutoScroll::MouseDir);
|
||||
|
||||
// Get the position of the mouse in the sprite
|
||||
gfx::Point spritePos = editor->screenToEditor(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);
|
||||
gfx::PointF spritePos = editor->screenToEditorF(mousePos);
|
||||
|
||||
// Get the customization for the pixels movement (snap to grid, angle snap, etc.).
|
||||
KeyContext keyContext = KeyContext::Normal;
|
||||
@ -402,15 +399,17 @@ bool MovingPixelsState::onMouseMove(Editor* editor, MouseMessage* msg)
|
||||
keyContext = KeyContext::ScalingSelection;
|
||||
break;
|
||||
case RotateNWHandle:
|
||||
case RotateNHandle:
|
||||
case RotateNEHandle:
|
||||
case RotateWHandle:
|
||||
case RotateEHandle:
|
||||
case RotateSWHandle:
|
||||
case RotateSHandle:
|
||||
case RotateSEHandle:
|
||||
keyContext = KeyContext::RotatingSelection;
|
||||
break;
|
||||
case SkewNHandle:
|
||||
case SkewWHandle:
|
||||
case SkewEHandle:
|
||||
case SkewSHandle:
|
||||
keyContext = KeyContext::ScalingSelection;
|
||||
break;
|
||||
}
|
||||
|
||||
PixelsMovement::MoveModifier moveModifier = PixelsMovement::NormalMovement;
|
||||
@ -432,14 +431,25 @@ bool MovingPixelsState::onMouseMove(Editor* editor, MouseMessage* msg)
|
||||
if (int(action & KeyAction::LockAxis))
|
||||
moveModifier |= PixelsMovement::LockAxisMovement;
|
||||
|
||||
if (int(action & KeyAction::FineControl))
|
||||
moveModifier |= PixelsMovement::FineControl;
|
||||
|
||||
m_renderTimer.start();
|
||||
m_pixelsMovement->setFastMode(true);
|
||||
|
||||
// Invalidate handles
|
||||
Decorator* decorator = static_cast<Decorator*>(editor->decorator());
|
||||
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
|
||||
m_pixelsMovement->moveImage(spritePos, moveModifier);
|
||||
|
||||
// Update context bar and status bar
|
||||
ContextBar* contextBar = App::instance()->contextBar();
|
||||
contextBar->updateForMovingPixels(transformation);
|
||||
|
||||
editor->updateStatusBar();
|
||||
return true;
|
||||
}
|
||||
@ -586,7 +596,7 @@ void MovingPixelsState::onBeforeCommandExecution(CommandExecutionEvent& ev)
|
||||
m_pixelsMovement->shift(delta.x, delta.y);
|
||||
}
|
||||
else {
|
||||
translate(delta);
|
||||
translate(gfx::PointF(delta));
|
||||
}
|
||||
// We've processed the selection content movement right here.
|
||||
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)
|
||||
{
|
||||
ASSERT(m_pixelsMovement);
|
||||
|
@ -29,7 +29,8 @@ namespace app {
|
||||
class MovingPixelsState
|
||||
: public StandbyState
|
||||
, EditorObserver
|
||||
, ContextBarObserver {
|
||||
, ContextBarObserver
|
||||
, PixelsMovementDelegate {
|
||||
public:
|
||||
MovingPixelsState(Editor* editor, ui::MouseMessage* msg, PixelsMovementPtr pixelsMovement, HandleType handle);
|
||||
virtual ~MovingPixelsState();
|
||||
@ -38,11 +39,13 @@ namespace app {
|
||||
return m_pixelsMovement->canHandleFrameChange();
|
||||
}
|
||||
|
||||
void translate(const gfx::Point& delta);
|
||||
void translate(const gfx::PointF& delta);
|
||||
void rotate(double angle);
|
||||
void flip(doc::algorithm::FlipType flipType);
|
||||
void shift(int dx, int dy);
|
||||
|
||||
void updateTransformation(const Transformation& t);
|
||||
|
||||
// EditorState
|
||||
virtual void onEnterState(Editor* editor) override;
|
||||
virtual void onEditorGotFocus(Editor* editor) override;
|
||||
@ -66,6 +69,9 @@ namespace app {
|
||||
// ContextBarObserver
|
||||
virtual void onDropPixels(ContextBarObserver::DropAction action) override;
|
||||
|
||||
// PixelsMovementDelegate
|
||||
virtual void onPivotChange() override;
|
||||
|
||||
virtual Transformation getTransformation(Editor* editor) override;
|
||||
|
||||
private:
|
||||
@ -98,7 +104,7 @@ namespace app {
|
||||
// Position of the mouse in the canvas to avoid redrawing when the
|
||||
// mouse position changes (only we redraw when the canvas position
|
||||
// changes).
|
||||
gfx::Point m_oldSpritePos;
|
||||
gfx::PointF m_oldSpritePos;
|
||||
|
||||
obs::connection m_ctxConn;
|
||||
obs::connection m_opaqueConn;
|
||||
|
@ -1,4 +1,5 @@
|
||||
// Aseprite
|
||||
// Copyright (C) 2020 Igara Studio S.A.
|
||||
// Copyright (C) 2001-2016 David Capello
|
||||
//
|
||||
// This program is distributed under the terms of
|
||||
@ -17,8 +18,7 @@ namespace app {
|
||||
|
||||
void set_pivot_from_preferences(Transformation& t)
|
||||
{
|
||||
Transformation::Corners corners;
|
||||
t.transformBox(corners);
|
||||
auto corners = t.transformedCorners();
|
||||
gfx::PointT<double> nw(corners[Transformation::Corners::LEFT_TOP]);
|
||||
gfx::PointT<double> ne(corners[Transformation::Corners::RIGHT_TOP]);
|
||||
gfx::PointT<double> sw(corners[Transformation::Corners::LEFT_BOTTOM]);
|
||||
|
@ -53,11 +53,17 @@
|
||||
#define DUMP_INNER_CMDS()
|
||||
#endif
|
||||
|
||||
using vec2 = base::Vector2d<double>;
|
||||
|
||||
namespace app {
|
||||
|
||||
template<typename T>
|
||||
static inline const base::Vector2d<double> point2Vector(const gfx::PointT<T>& pt) {
|
||||
return base::Vector2d<double>(pt.x, pt.y);
|
||||
static inline const vec2 to_vec2(const gfx::PointT<T>& pt) {
|
||||
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)
|
||||
@ -180,6 +186,11 @@ bool PixelsMovement::editMultipleCels() const
|
||||
m_site.range().type() == DocRange::kCels));
|
||||
}
|
||||
|
||||
void PixelsMovement::setDelegate(PixelsMovementDelegate* delegate)
|
||||
{
|
||||
m_delegate = delegate;
|
||||
}
|
||||
|
||||
void PixelsMovement::setFastMode(const bool 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()
|
||||
{
|
||||
ContextWriter writer(m_reader, 1000);
|
||||
@ -296,7 +350,7 @@ void PixelsMovement::copyMask()
|
||||
hideDocumentMask();
|
||||
}
|
||||
|
||||
void PixelsMovement::catchImage(const gfx::Point& pos, HandleType handle)
|
||||
void PixelsMovement::catchImage(const gfx::PointF& pos, HandleType handle)
|
||||
{
|
||||
ASSERT(handle != NoHandle);
|
||||
|
||||
@ -305,7 +359,7 @@ void PixelsMovement::catchImage(const gfx::Point& pos, HandleType 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
|
||||
m_initialData = m_currentData;
|
||||
@ -316,33 +370,33 @@ void PixelsMovement::catchImageAgain(const gfx::Point& pos, HandleType handle)
|
||||
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);
|
||||
gfx::RectF bounds = m_initialData.bounds();
|
||||
bool updateBounds = false;
|
||||
double dx, dy;
|
||||
gfx::PointF abs_initial_pivot = m_initialData.pivot();
|
||||
gfx::PointF abs_pivot = m_currentData.pivot();
|
||||
|
||||
dx = ((pos.x - m_catchPos.x) * cos(m_currentData.angle()) +
|
||||
(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()));
|
||||
auto newTransformation = m_currentData;
|
||||
|
||||
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 (ABS(dx) < ABS(dy))
|
||||
if (std::abs(dx) < std::abs(dy))
|
||||
dx = 0.0;
|
||||
else
|
||||
dy = 0.0;
|
||||
}
|
||||
|
||||
bounds.offset(dx, dy);
|
||||
updateBounds = true;
|
||||
|
||||
if ((moveModifier & SnapToGridMovement) == SnapToGridMovement) {
|
||||
// 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
|
||||
// use it to adjust all coordinates (x1, y1, x2, y2).
|
||||
gridOffset -= bounds.origin();
|
||||
bounds.offset(gridOffset);
|
||||
bounds.setOrigin(gridOffset);
|
||||
}
|
||||
|
||||
newTransformation.bounds(bounds);
|
||||
newTransformation.pivot(abs_initial_pivot +
|
||||
bounds.origin() -
|
||||
m_initialData.bounds().origin());
|
||||
break;
|
||||
}
|
||||
|
||||
case ScaleNWHandle:
|
||||
case ScaleNHandle:
|
||||
@ -379,8 +438,7 @@ void PixelsMovement::moveImage(const gfx::Point& pos, MoveModifier moveModifier)
|
||||
handles[m_handle-ScaleNWHandle][1]);
|
||||
|
||||
if ((moveModifier & ScaleFromPivot) == ScaleFromPivot) {
|
||||
pivot.x = m_currentData.pivot().x;
|
||||
pivot.y = m_currentData.pivot().y;
|
||||
pivot = m_currentData.pivot();
|
||||
}
|
||||
else {
|
||||
pivot.x = 1.0 - handle.x;
|
||||
@ -393,26 +451,36 @@ void PixelsMovement::moveImage(const gfx::Point& pos, MoveModifier moveModifier)
|
||||
gfx::PointF b = bounds.point2();
|
||||
|
||||
if ((moveModifier & MaintainAspectRatioMovement) == MaintainAspectRatioMovement) {
|
||||
auto u = point2Vector(gfx::PointF(m_catchPos) - pivot);
|
||||
auto v = point2Vector(gfx::PointF(pos) - pivot);
|
||||
auto w = v.projectOn(u);
|
||||
vec2 u = to_vec2(m_catchPos - pivot);
|
||||
vec2 v = to_vec2(pos - pivot);
|
||||
vec2 w = v.projectOn(u);
|
||||
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;
|
||||
}
|
||||
else
|
||||
scale = 1.0;
|
||||
|
||||
a.x = int((a.x-pivot.x)*scale + pivot.x);
|
||||
a.y = int((a.y-pivot.y)*scale + pivot.y);
|
||||
b.x = int((b.x-pivot.x)*scale + pivot.x);
|
||||
b.y = int((b.y-pivot.y)*scale + pivot.y);
|
||||
a.x = ((a.x-pivot.x)*scale + pivot.x);
|
||||
a.y = ((a.y-pivot.y)*scale + pivot.y);
|
||||
b.x = ((b.x-pivot.x)*scale + pivot.x);
|
||||
b.y = ((b.y-pivot.y)*scale + pivot.y);
|
||||
}
|
||||
else {
|
||||
handle.x = bounds.x + bounds.w*handle.x;
|
||||
handle.y = bounds.y + bounds.h*handle.y;
|
||||
|
||||
double z = m_currentData.angle();
|
||||
double w = (handle.x-pivot.x);
|
||||
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) {
|
||||
dx = 0.0;
|
||||
@ -423,10 +491,10 @@ void PixelsMovement::moveImage(const gfx::Point& pos, MoveModifier moveModifier)
|
||||
h = 1.0;
|
||||
}
|
||||
|
||||
a.x = int((a.x-pivot.x)*(1.0+dx/w) + pivot.x);
|
||||
a.y = int((a.y-pivot.y)*(1.0+dy/h) + pivot.y);
|
||||
b.x = int((b.x-pivot.x)*(1.0+dx/w) + pivot.x);
|
||||
b.y = int((b.y-pivot.y)*(1.0+dy/h) + pivot.y);
|
||||
a.x = ((a.x-pivot.x)*(1.0+dx/w) + pivot.x);
|
||||
a.y = ((a.y-pivot.y)*(1.0+dy/h) + pivot.y);
|
||||
b.x = ((b.x-pivot.x)*(1.0+dx/w) + pivot.x);
|
||||
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
|
||||
@ -437,109 +505,210 @@ void PixelsMovement::moveImage(const gfx::Point& pos, MoveModifier moveModifier)
|
||||
bounds.w = b.x - a.x;
|
||||
bounds.h = b.y - a.y;
|
||||
|
||||
updateBounds = true;
|
||||
newTransformation.bounds(bounds);
|
||||
m_adjustPivot = true;
|
||||
break;
|
||||
}
|
||||
|
||||
case RotateNWHandle:
|
||||
case RotateNHandle:
|
||||
case RotateNEHandle:
|
||||
case RotateWHandle:
|
||||
case RotateEHandle:
|
||||
case RotateSWHandle:
|
||||
case RotateSHandle:
|
||||
case RotateSEHandle:
|
||||
{
|
||||
gfx::PointF abs_initial_pivot = m_initialData.pivot();
|
||||
gfx::PointF abs_pivot = m_currentData.pivot();
|
||||
case RotateSEHandle: {
|
||||
double da = (std::atan2((double)(-pos.y + abs_pivot.y),
|
||||
(double)(+pos.x - abs_pivot.x)) -
|
||||
std::atan2((double)(-m_catchPos.y + abs_initial_pivot.y),
|
||||
(double)(+m_catchPos.x - abs_initial_pivot.x)));
|
||||
double newAngle = m_initialData.angle() + da;
|
||||
newAngle = base::fmod_radians(newAngle);
|
||||
|
||||
double newAngle =
|
||||
m_initialData.angle()
|
||||
+ atan2((double)(-pos.y + abs_pivot.y),
|
||||
(double)(+pos.x - abs_pivot.x))
|
||||
- atan2((double)(-m_catchPos.y + abs_initial_pivot.y),
|
||||
(double)(+m_catchPos.x - abs_initial_pivot.x));
|
||||
// Is the "angle snap" is activated, we've to snap the angle
|
||||
// to common (pixel art) angles.
|
||||
if ((moveModifier & AngleSnapMovement) == AngleSnapMovement) {
|
||||
// TODO make this configurable
|
||||
static const double keyAngles[] = {
|
||||
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
|
||||
// to common (pixel art) angles.
|
||||
if ((moveModifier & AngleSnapMovement) == AngleSnapMovement) {
|
||||
// TODO make this configurable
|
||||
static const double keyAngles[] = {
|
||||
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
|
||||
};
|
||||
|
||||
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;
|
||||
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;
|
||||
}
|
||||
|
||||
m_currentData.angle(newAngle);
|
||||
newAngle = PI * keyAngles[closest] / 180.0;
|
||||
}
|
||||
|
||||
newTransformation.angle(newAngle);
|
||||
break;
|
||||
}
|
||||
|
||||
case PivotHandle:
|
||||
{
|
||||
// Calculate the new position of the pivot
|
||||
gfx::PointF newPivot(m_initialData.pivot().x + (pos.x - m_catchPos.x),
|
||||
m_initialData.pivot().y + (pos.y - m_catchPos.y));
|
||||
case SkewNHandle:
|
||||
case SkewSHandle:
|
||||
case SkewWHandle:
|
||||
case SkewEHandle: {
|
||||
// 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;
|
||||
m_currentData.displacePivotTo(newPivot);
|
||||
// Pivot in pixels
|
||||
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;
|
||||
}
|
||||
|
||||
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) {
|
||||
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());
|
||||
}
|
||||
setTransformationBase(newTransformation);
|
||||
}
|
||||
|
||||
void PixelsMovement::getDraggedImageCopy(std::unique_ptr<Image>& outputImage,
|
||||
std::unique_ptr<Mask>& outputMask)
|
||||
{
|
||||
gfx::Rect bounds = m_currentData.transformedBounds();
|
||||
if (bounds.isEmpty())
|
||||
return;
|
||||
|
||||
std::unique_ptr<Image> image(
|
||||
Image::create(
|
||||
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
|
||||
// "image" render.
|
||||
@ -623,8 +792,11 @@ void PixelsMovement::stampImage(bool finalStamp)
|
||||
|
||||
void PixelsMovement::stampExtraCelImage()
|
||||
{
|
||||
const Cel* cel = m_extraCel->cel();
|
||||
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
|
||||
// portion of sprite.
|
||||
@ -664,27 +836,10 @@ void PixelsMovement::dropImageTemporarily()
|
||||
// Displace the pivot to the new site:
|
||||
if (m_adjustPivot) {
|
||||
m_adjustPivot = false;
|
||||
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.
|
||||
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));
|
||||
if (m_delegate)
|
||||
m_delegate->onPivotChange();
|
||||
}
|
||||
|
||||
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()
|
||||
{
|
||||
m_isDragging = false;
|
||||
@ -780,28 +958,35 @@ void PixelsMovement::redrawExtraImage(Transformation* transformation)
|
||||
Cel* cel = m_site.cel();
|
||||
if (cel) opacity = MUL_UN8(opacity, cel->opacity(), t);
|
||||
|
||||
gfx::Rect bounds = transformation->transformedBounds();
|
||||
|
||||
if (!m_extraCel)
|
||||
m_extraCel.reset(new ExtraCel);
|
||||
|
||||
m_extraCel->create(
|
||||
m_site.tilemapMode(),
|
||||
m_document->sprite(),
|
||||
bounds,
|
||||
(m_site.tilemapMode() == TilemapMode::Tiles ? m_site.grid().tileToCanvas(bounds).size():
|
||||
bounds.size()),
|
||||
m_site.frame(),
|
||||
opacity);
|
||||
m_extraCel->setType(render::ExtraType::PATCH);
|
||||
m_extraCel->setBlendMode(m_site.layer()->isImage() ?
|
||||
static_cast<LayerImage*>(m_site.layer())->blendMode():
|
||||
BlendMode::NORMAL);
|
||||
gfx::Rect bounds = transformation->transformedBounds();
|
||||
if (!bounds.isEmpty()) {
|
||||
m_extraCel->create(
|
||||
m_site.tilemapMode(),
|
||||
m_document->sprite(),
|
||||
bounds,
|
||||
(m_site.tilemapMode() == TilemapMode::Tiles ? m_site.grid().tileToCanvas(bounds).size():
|
||||
bounds.size()),
|
||||
m_site.frame(),
|
||||
opacity);
|
||||
m_extraCel->setType(render::ExtraType::PATCH);
|
||||
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);
|
||||
|
||||
// Draw the transformed pixels in the extra-cel which is the chunk
|
||||
// of pixels that the user is moving.
|
||||
drawImage(*transformation, m_extraCel->image(), bounds.origin(), true);
|
||||
if (m_extraCel->image()) {
|
||||
// Draw the transformed pixels in the extra-cel which is the chunk
|
||||
// of pixels that the user is moving.
|
||||
drawImage(*transformation, m_extraCel->image(),
|
||||
gfx::PointF(bounds.origin()), true);
|
||||
}
|
||||
}
|
||||
|
||||
void PixelsMovement::redrawCurrentMask()
|
||||
@ -811,13 +996,12 @@ void PixelsMovement::redrawCurrentMask()
|
||||
|
||||
void PixelsMovement::drawImage(
|
||||
const Transformation& transformation,
|
||||
doc::Image* dst, const gfx::Point& pt,
|
||||
doc::Image* dst, const gfx::PointF& pt,
|
||||
const bool renderOriginalLayer)
|
||||
{
|
||||
ASSERT(dst);
|
||||
|
||||
Transformation::Corners corners;
|
||||
transformation.transformBox(corners);
|
||||
auto corners = transformation.transformedCorners();
|
||||
gfx::Rect bounds = corners.bounds();
|
||||
|
||||
dst->setMaskColor(m_site.sprite()->transparentColor());
|
||||
@ -854,10 +1038,14 @@ void PixelsMovement::drawImage(
|
||||
|
||||
void PixelsMovement::drawMask(doc::Mask* mask, bool shrink)
|
||||
{
|
||||
Transformation::Corners corners;
|
||||
m_currentData.transformBox(corners);
|
||||
auto corners = m_currentData.transformedCorners();
|
||||
gfx::Rect bounds = corners.bounds();
|
||||
|
||||
if (bounds.isEmpty()) {
|
||||
mask->clear();
|
||||
return;
|
||||
}
|
||||
|
||||
mask->replace(bounds);
|
||||
if (shrink)
|
||||
mask->freeze();
|
||||
@ -866,7 +1054,8 @@ void PixelsMovement::drawMask(doc::Mask* mask, bool shrink)
|
||||
mask->bitmap(),
|
||||
m_initialMask->bitmap(),
|
||||
nullptr,
|
||||
corners, bounds.origin());
|
||||
corners,
|
||||
gfx::PointF(bounds.origin()));
|
||||
if (shrink)
|
||||
mask->unfreeze();
|
||||
}
|
||||
@ -875,7 +1064,7 @@ void PixelsMovement::drawParallelogram(
|
||||
const Transformation& transformation,
|
||||
doc::Image* dst, const doc::Image* src, const doc::Mask* mask,
|
||||
const Transformation::Corners& corners,
|
||||
const gfx::Point& leftTop)
|
||||
const gfx::PointF& leftTop)
|
||||
{
|
||||
tools::RotationAlgorithm rotAlgo = Preferences::instance().selection.rotationAlgorithm();
|
||||
|
||||
@ -943,6 +1132,9 @@ void PixelsMovement::onPivotChange()
|
||||
{
|
||||
set_pivot_from_preferences(m_currentData);
|
||||
onRotationAlgorithmChange();
|
||||
|
||||
if (m_delegate)
|
||||
m_delegate->onPivotChange();
|
||||
}
|
||||
|
||||
void PixelsMovement::onRotationAlgorithmChange()
|
||||
|
@ -1,5 +1,5 @@
|
||||
// Aseprite
|
||||
// Copyright (C) 2019 Igara Studio S.A.
|
||||
// Copyright (C) 2019-2020 Igara Studio S.A.
|
||||
// Copyright (C) 2001-2018 David Capello
|
||||
//
|
||||
// This program is distributed under the terms of
|
||||
@ -36,6 +36,12 @@ namespace app {
|
||||
class SetMask;
|
||||
}
|
||||
|
||||
class PixelsMovementDelegate {
|
||||
public:
|
||||
virtual ~PixelsMovementDelegate() { }
|
||||
virtual void onPivotChange() = 0;
|
||||
};
|
||||
|
||||
// Helper class to move pixels interactively and control undo history
|
||||
// correctly. The extra cel of the sprite is temporally used to show
|
||||
// feedback, drag, and drop the specified image in the constructor
|
||||
@ -50,6 +56,7 @@ namespace app {
|
||||
MaintainAspectRatioMovement = 8,
|
||||
LockAxisMovement = 16,
|
||||
ScaleFromPivot = 32,
|
||||
FineControl = 64,
|
||||
};
|
||||
|
||||
enum CommitChangesOption {
|
||||
@ -71,17 +78,18 @@ namespace app {
|
||||
HandleType handle() const { return m_handle; }
|
||||
bool canHandleFrameChange() const { return m_canHandleFrameChange; }
|
||||
|
||||
void setDelegate(PixelsMovementDelegate* delegate);
|
||||
void setFastMode(const bool fastMode);
|
||||
|
||||
void trim();
|
||||
void cutMask();
|
||||
void copyMask();
|
||||
void catchImage(const gfx::Point& pos, HandleType handle);
|
||||
void catchImageAgain(const gfx::Point& pos, HandleType handle);
|
||||
void catchImage(const gfx::PointF& pos, HandleType handle);
|
||||
void catchImageAgain(const gfx::PointF& pos, HandleType handle);
|
||||
|
||||
// Moves the image to the new position (relative to the start
|
||||
// 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
|
||||
// current transformation.
|
||||
@ -118,8 +126,11 @@ namespace app {
|
||||
bool gotoFrame(const doc::frame_t deltaFrame);
|
||||
|
||||
const Transformation& getTransformation() const { return m_currentData; }
|
||||
void setTransformation(const Transformation& t);
|
||||
|
||||
private:
|
||||
void setTransformationBase(const Transformation& t);
|
||||
void adjustPivot();
|
||||
bool editMultipleCels() const;
|
||||
void stampImage(bool finalStamp);
|
||||
void stampExtraCelImage();
|
||||
@ -129,14 +140,14 @@ namespace app {
|
||||
void redrawCurrentMask();
|
||||
void drawImage(
|
||||
const Transformation& transformation,
|
||||
doc::Image* dst, const gfx::Point& pos,
|
||||
doc::Image* dst, const gfx::PointF& pos,
|
||||
const bool renderOriginalLayer);
|
||||
void drawMask(doc::Mask* dst, bool shrink);
|
||||
void drawParallelogram(
|
||||
const Transformation& transformation,
|
||||
doc::Image* dst, const doc::Image* src, const doc::Mask* mask,
|
||||
const Transformation::Corners& corners,
|
||||
const gfx::Point& leftTop);
|
||||
const gfx::PointF& leftTop);
|
||||
void updateDocumentMask();
|
||||
void hideDocumentMask();
|
||||
|
||||
@ -149,6 +160,7 @@ namespace app {
|
||||
void dumpInnerCmds();
|
||||
#endif
|
||||
|
||||
PixelsMovementDelegate* m_delegate = nullptr;
|
||||
const ContextReader m_reader;
|
||||
Site m_site;
|
||||
Doc* m_document;
|
||||
@ -157,7 +169,7 @@ namespace app {
|
||||
bool m_adjustPivot;
|
||||
HandleType m_handle;
|
||||
doc::ImageRef m_originalImage;
|
||||
gfx::Point m_catchPos;
|
||||
gfx::PointF m_catchPos;
|
||||
Transformation m_initialData;
|
||||
Transformation m_currentData;
|
||||
std::unique_ptr<Mask> m_initialMask, m_initialMask0;
|
||||
|
@ -51,13 +51,13 @@
|
||||
#include "app/util/layer_utils.h"
|
||||
#include "app/util/new_image_from_mask.h"
|
||||
#include "app/util/readable_time.h"
|
||||
#include "base/clamp.h"
|
||||
#include "base/pi.h"
|
||||
#include "doc/grid.h"
|
||||
#include "doc/layer.h"
|
||||
#include "doc/mask.h"
|
||||
#include "doc/slice.h"
|
||||
#include "doc/sprite.h"
|
||||
#include "fixmath/fixmath.h"
|
||||
#include "fmt/format.h"
|
||||
#include "gfx/rect.h"
|
||||
#include "os/surface.h"
|
||||
@ -74,17 +74,6 @@ namespace app {
|
||||
|
||||
using namespace ui;
|
||||
|
||||
static CursorType rotated_size_cursors[8] = {
|
||||
kSizeECursor,
|
||||
kSizeNECursor,
|
||||
kSizeNCursor,
|
||||
kSizeNWCursor,
|
||||
kSizeWCursor,
|
||||
kSizeSWCursor,
|
||||
kSizeSCursor,
|
||||
kSizeSECursor
|
||||
};
|
||||
|
||||
#ifdef _MSC_VER
|
||||
#pragma warning(disable:4355) // warning C4355: 'this' : used in base member initializer list
|
||||
#endif
|
||||
@ -714,7 +703,7 @@ void StandbyState::startSelectionTransformation(Editor* editor,
|
||||
transformSelection(editor, NULL, NoHandle);
|
||||
|
||||
if (MovingPixelsState* movingPixels = dynamic_cast<MovingPixelsState*>(editor->getState().get())) {
|
||||
movingPixels->translate(move);
|
||||
movingPixels->translate(gfx::PointF(move));
|
||||
if (std::fabs(angle) > 1e-5)
|
||||
movingPixels->rotate(angle);
|
||||
}
|
||||
@ -905,65 +894,77 @@ bool StandbyState::Decorator::onSetCursor(tools::Ink* ink, Editor* editor, const
|
||||
|
||||
CursorType newCursorType = kArrowCursor;
|
||||
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) {
|
||||
case ScaleNWHandle: newCursorType = kSizeNWCursor; break;
|
||||
case ScaleNHandle: newCursorType = kSizeNCursor; break;
|
||||
case ScaleNEHandle: newCursorType = kSizeNECursor; break;
|
||||
case ScaleWHandle: newCursorType = kSizeWCursor; break;
|
||||
case ScaleEHandle: newCursorType = kSizeECursor; break;
|
||||
case ScaleSWHandle: newCursorType = kSizeSWCursor; break;
|
||||
case ScaleSHandle: newCursorType = kSizeSCursor; break;
|
||||
case ScaleSEHandle: newCursorType = kSizeSECursor; break;
|
||||
case RotateNWHandle: newCursor = theme->cursors.rotateNw(); break;
|
||||
case RotateNHandle: newCursor = theme->cursors.rotateN(); break;
|
||||
case RotateNEHandle: newCursor = theme->cursors.rotateNe(); break;
|
||||
case RotateWHandle: newCursor = theme->cursors.rotateW(); break;
|
||||
case RotateEHandle: newCursor = theme->cursors.rotateE(); break;
|
||||
case RotateSWHandle: newCursor = theme->cursors.rotateSw(); break;
|
||||
case RotateSHandle: newCursor = theme->cursors.rotateS(); break;
|
||||
case RotateSEHandle: newCursor = theme->cursors.rotateSe(); break;
|
||||
case PivotHandle: newCursorType = kHandCursor; break;
|
||||
case ScaleNWHandle: newCursorType = kSizeNWCursor; break;
|
||||
case ScaleNHandle: newCursorType = kSizeNCursor; break;
|
||||
case ScaleNEHandle: newCursorType = kSizeNECursor; break;
|
||||
case ScaleWHandle: newCursorType = kSizeWCursor; break;
|
||||
case ScaleEHandle: newCursorType = kSizeECursor; break;
|
||||
case ScaleSWHandle: newCursorType = kSizeSWCursor; break;
|
||||
case ScaleSHandle: newCursorType = kSizeSCursor; break;
|
||||
case ScaleSEHandle: newCursorType = kSizeSECursor; break;
|
||||
case PivotHandle: newCursorType = kHandCursor; break;
|
||||
case RotateNWHandle:
|
||||
case RotateNEHandle:
|
||||
case RotateSWHandle:
|
||||
case RotateSEHandle:
|
||||
case SkewNHandle:
|
||||
case SkewWHandle:
|
||||
case SkewEHandle:
|
||||
case SkewSHandle:
|
||||
break;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
|
||||
// Adjust the cursor depending the current transformation angle.
|
||||
fixmath::fixed angle = fixmath::ftofix(128.0 * transformation.angle() / PI);
|
||||
angle = fixmath::fixadd(angle, fixmath::itofix(16));
|
||||
angle &= (255<<16);
|
||||
angle >>= 16;
|
||||
angle /= 32;
|
||||
|
||||
if (newCursorType >= kSizeNCursor &&
|
||||
newCursorType <= kSizeNWCursor) {
|
||||
size_t num = sizeof(rotated_size_cursors) / sizeof(rotated_size_cursors[0]);
|
||||
size_t c;
|
||||
for (c=num-1; c>0; --c)
|
||||
if (rotated_size_cursors[c] == newCursorType)
|
||||
break;
|
||||
|
||||
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()
|
||||
const CursorType rotated_size_cursors[8] = {
|
||||
kSizeWCursor,
|
||||
kSizeSWCursor, kSizeSCursor, kSizeSECursor,
|
||||
kSizeECursor,
|
||||
kSizeNECursor, kSizeNCursor, kSizeNWCursor,
|
||||
};
|
||||
size_t num = sizeof(rotated_rotate_cursors) / sizeof(rotated_rotate_cursors[0]);
|
||||
size_t c;
|
||||
for (c=num-1; c>0; --c)
|
||||
if (rotated_rotate_cursors[c] == newCursor)
|
||||
break;
|
||||
|
||||
newCursor = rotated_rotate_cursors[(c+angle) % num];
|
||||
newCursorType = rotated_size_cursors[angle];
|
||||
}
|
||||
else if (handle >= RotateNWHandle &&
|
||||
handle <= RotateSEHandle) {
|
||||
const Cursor* rotated_rotate_cursors[8] = {
|
||||
theme->cursors.rotateW(),
|
||||
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;
|
||||
}
|
||||
|
||||
|
@ -42,13 +42,13 @@ static struct HandlesInfo {
|
||||
// The exact handle type ([0] for scaling, [1] for rotating).
|
||||
HandleType handle[2];
|
||||
} handles_info[HANDLES] = {
|
||||
{ 1, 2, 0 << 16, { ScaleEHandle, RotateEHandle } },
|
||||
{ 1, 2, 0 << 16, { ScaleEHandle, SkewEHandle } },
|
||||
{ 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, 3, 128 << 16, { ScaleWHandle, RotateWHandle } },
|
||||
{ 0, 3, 128 << 16, { ScaleWHandle, SkewWHandle } },
|
||||
{ 3, 3, 160 << 16, { ScaleSWHandle, RotateSWHandle } },
|
||||
{ 3, 2, 192 << 16, { ScaleSHandle, RotateSHandle } },
|
||||
{ 3, 2, 192 << 16, { ScaleSHandle, SkewSHandle } },
|
||||
{ 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);
|
||||
fixmath::fixed angle = fixmath::ftofix(128.0 * transform.angle() / PI);
|
||||
|
||||
Transformation::Corners corners;
|
||||
transform.transformBox(corners);
|
||||
auto corners = transform.transformedCorners();
|
||||
|
||||
std::vector<gfx::Point> 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);
|
||||
|
||||
Transformation::Corners corners;
|
||||
transform.transformBox(corners);
|
||||
auto corners = transform.transformedCorners();
|
||||
|
||||
std::vector<gfx::Point> screenPoints;
|
||||
getScreenPoints(editor, corners, screenPoints);
|
||||
@ -99,8 +97,7 @@ void TransformHandles::drawHandles(Editor* editor, ui::Graphics* g,
|
||||
const gfx::Point origin = editor->bounds().origin();
|
||||
|
||||
#if 0 // Uncomment this if you want to see the bounds in red (only for debugging purposes)
|
||||
// -----------------------------------------------
|
||||
{
|
||||
{ // Bounds
|
||||
gfx::Point
|
||||
a(transform.bounds().origin()),
|
||||
b(transform.bounds().point2());
|
||||
@ -112,7 +109,26 @@ void TransformHandles::drawHandles(Editor* editor, ui::Graphics* g,
|
||||
a = editor->editorToScreen(a) - origin;
|
||||
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
|
||||
|
||||
// Draw corner handle
|
||||
@ -141,8 +157,7 @@ void TransformHandles::invalidateHandles(Editor* editor, const Transformation& t
|
||||
SkinTheme* theme = SkinTheme::instance();
|
||||
fixmath::fixed angle = fixmath::ftofix(128.0 * transform.angle() / PI);
|
||||
|
||||
Transformation::Corners corners;
|
||||
transform.transformBox(corners);
|
||||
auto corners = transform.transformedCorners();
|
||||
|
||||
std::vector<gfx::Point> screenPoints;
|
||||
getScreenPoints(editor, corners, screenPoints);
|
||||
@ -266,9 +281,9 @@ void TransformHandles::getScreenPoints(
|
||||
|
||||
screenPoints.resize(corners.size());
|
||||
for (size_t c=0; c<corners.size(); ++c)
|
||||
screenPoints[c] = editor->editorToScreen(
|
||||
gfx::Point((int)corners[c].x+main.x,
|
||||
(int)corners[c].y+main.y));
|
||||
screenPoints[c] = editor->editorToScreenF(
|
||||
gfx::PointF(corners[c].x+main.x,
|
||||
corners[c].y+main.y));
|
||||
}
|
||||
|
||||
} // namespace app
|
||||
|
@ -1,4 +1,5 @@
|
||||
// Aseprite
|
||||
// Copyright (C) 2020 Igara Studio S.A.
|
||||
// Copyright (C) 2018 David Capello
|
||||
//
|
||||
// This program is distributed under the terms of
|
||||
@ -12,6 +13,7 @@
|
||||
|
||||
#include "ui/message.h"
|
||||
|
||||
#include "fmt/format.h"
|
||||
#include "tinyexpr.h"
|
||||
|
||||
#include <cmath>
|
||||
@ -29,14 +31,8 @@ bool ExprEntry::onProcessMessage(ui::Message* msg)
|
||||
{
|
||||
switch (msg->type()) {
|
||||
case ui::kFocusLeaveMessage: {
|
||||
char buf[256];
|
||||
if (m_decimals == 0) {
|
||||
std::snprintf(buf, sizeof(buf), "%d", onGetTextInt());
|
||||
}
|
||||
else {
|
||||
std::snprintf(buf, sizeof(buf), "%.*g",
|
||||
m_decimals, onGetTextDouble());
|
||||
}
|
||||
std::string buf;
|
||||
onFormatExprFocusLeave(buf);
|
||||
if (text() != buf)
|
||||
setText(buf);
|
||||
break;
|
||||
@ -71,4 +67,12 @@ double ExprEntry::onGetTextDouble() const
|
||||
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
|
||||
|
@ -26,6 +26,8 @@ namespace app {
|
||||
int onGetTextInt() const override;
|
||||
double onGetTextDouble() const override;
|
||||
|
||||
virtual void onFormatExprFocusLeave(std::string& buf);
|
||||
|
||||
int m_decimals;
|
||||
};
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
// Aseprite
|
||||
// Copyright (C) 2018 Igara Studio S.A.
|
||||
// Copyright (C) 2018-2020 Igara Studio S.A.
|
||||
// Copyright (C) 2001-2018 David Capello
|
||||
//
|
||||
// This program is distributed under the terms of
|
||||
@ -63,6 +63,7 @@ namespace app {
|
||||
ScaleFromCenter = 0x00008000,
|
||||
AngleSnapFromLastPoint = 0x00010000,
|
||||
RotateShape = 0x00020000,
|
||||
FineControl = 0x00040000,
|
||||
};
|
||||
|
||||
enum class WheelAction {
|
||||
@ -100,10 +101,12 @@ namespace app {
|
||||
|
||||
class Key {
|
||||
public:
|
||||
Key(Command* command, const Params& params, KeyContext keyContext);
|
||||
Key(KeyType type, tools::Tool* tool);
|
||||
explicit Key(KeyAction action);
|
||||
explicit Key(WheelAction action);
|
||||
Key(Command* command, const Params& params,
|
||||
const KeyContext keyContext);
|
||||
Key(const KeyType type, tools::Tool* tool);
|
||||
explicit Key(const KeyAction action,
|
||||
const KeyContext keyContext);
|
||||
explicit Key(const WheelAction action);
|
||||
|
||||
KeyType type() const { return m_type; }
|
||||
const ui::Accelerators& accels() const {
|
||||
|
@ -40,26 +40,29 @@ namespace {
|
||||
const char* name;
|
||||
const char* userfriendly;
|
||||
app::KeyAction action;
|
||||
app::KeyContext context;
|
||||
} actions[] = {
|
||||
{ "CopySelection" , "Copy Selection" , app::KeyAction::CopySelection },
|
||||
{ "SnapToGrid" , "Snap To Grid" , app::KeyAction::SnapToGrid },
|
||||
{ "AngleSnap" , "Angle Snap" , app::KeyAction::AngleSnap },
|
||||
{ "MaintainAspectRatio" , "Maintain Aspect Ratio", app::KeyAction::MaintainAspectRatio },
|
||||
{ "ScaleFromCenter" , "Scale From Center" , app::KeyAction::ScaleFromCenter },
|
||||
{ "LockAxis" , "Lock Axis" , app::KeyAction::LockAxis },
|
||||
{ "AddSelection" , "Add Selection" , app::KeyAction::AddSelection },
|
||||
{ "SubtractSelection" , "Subtract Selection" , app::KeyAction::SubtractSelection },
|
||||
{ "IntersectSelection" , "Intersect Selection" , app::KeyAction::IntersectSelection },
|
||||
{ "AutoSelectLayer" , "Auto Select Layer" , app::KeyAction::AutoSelectLayer },
|
||||
{ "StraightLineFromLastPoint", "Straight Line from Last Point", app::KeyAction::StraightLineFromLastPoint },
|
||||
{ "AngleSnapFromLastPoint", "Angle Snap from Last Point", app::KeyAction::AngleSnapFromLastPoint },
|
||||
{ "MoveOrigin" , "Move Origin" , app::KeyAction::MoveOrigin },
|
||||
{ "SquareAspect" , "Square Aspect" , app::KeyAction::SquareAspect },
|
||||
{ "DrawFromCenter" , "Draw From Center" , app::KeyAction::DrawFromCenter },
|
||||
{ "RotateShape" , "Rotate Shape" , app::KeyAction::RotateShape },
|
||||
{ "LeftMouseButton" , "Trigger Left Mouse Button" , app::KeyAction::LeftMouseButton },
|
||||
{ "RightMouseButton" , "Trigger Right Mouse Button" , app::KeyAction::RightMouseButton },
|
||||
{ NULL , NULL , app::KeyAction::None }
|
||||
{ "CopySelection" , "Copy Selection" , app::KeyAction::CopySelection, app::KeyContext::TranslatingSelection },
|
||||
{ "SnapToGrid" , "Snap To Grid" , app::KeyAction::SnapToGrid, app::KeyContext::TranslatingSelection },
|
||||
{ "LockAxis" , "Lock Axis" , app::KeyAction::LockAxis, app::KeyContext::TranslatingSelection },
|
||||
{ "FineControl" , "Fine Translating" , app::KeyAction::FineControl , app::KeyContext::TranslatingSelection },
|
||||
{ "MaintainAspectRatio" , "Maintain Aspect Ratio", app::KeyAction::MaintainAspectRatio, app::KeyContext::ScalingSelection },
|
||||
{ "ScaleFromCenter" , "Scale From Center" , app::KeyAction::ScaleFromCenter, app::KeyContext::ScalingSelection },
|
||||
{ "FineControl" , "Fine Scaling" , app::KeyAction::FineControl , app::KeyContext::ScalingSelection },
|
||||
{ "AngleSnap" , "Angle Snap" , app::KeyAction::AngleSnap, app::KeyContext::RotatingSelection },
|
||||
{ "AddSelection" , "Add Selection" , app::KeyAction::AddSelection, app::KeyContext::SelectionTool },
|
||||
{ "SubtractSelection" , "Subtract Selection" , app::KeyAction::SubtractSelection, app::KeyContext::SelectionTool },
|
||||
{ "IntersectSelection" , "Intersect Selection" , app::KeyAction::IntersectSelection, app::KeyContext::SelectionTool },
|
||||
{ "AutoSelectLayer" , "Auto Select Layer" , app::KeyAction::AutoSelectLayer, app::KeyContext::MoveTool },
|
||||
{ "StraightLineFromLastPoint", "Straight Line from Last Point", app::KeyAction::StraightLineFromLastPoint, app::KeyContext::FreehandTool },
|
||||
{ "AngleSnapFromLastPoint", "Angle Snap from Last Point", app::KeyAction::AngleSnapFromLastPoint, app::KeyContext::FreehandTool },
|
||||
{ "MoveOrigin" , "Move Origin" , app::KeyAction::MoveOrigin, app::KeyContext::ShapeTool },
|
||||
{ "SquareAspect" , "Square Aspect" , app::KeyAction::SquareAspect, app::KeyContext::ShapeTool },
|
||||
{ "DrawFromCenter" , "Draw From Center" , app::KeyAction::DrawFromCenter, app::KeyContext::ShapeTool },
|
||||
{ "RotateShape" , "Rotate Shape" , app::KeyAction::RotateShape, app::KeyContext::ShapeTool },
|
||||
{ "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 {
|
||||
@ -124,9 +127,11 @@ namespace {
|
||||
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) {
|
||||
if (action == actions[c].action)
|
||||
if (action == actions[c].action &&
|
||||
context == actions[c].context)
|
||||
return actions[c].userfriendly;
|
||||
}
|
||||
return std::string();
|
||||
@ -203,7 +208,7 @@ using namespace ui;
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
// 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_useUsers(false)
|
||||
, 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_useUsers(false)
|
||||
, 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_useUsers(false)
|
||||
, m_keycontext(KeyContext::Any)
|
||||
, m_keycontext(keyContext)
|
||||
, m_action(action)
|
||||
{
|
||||
if (m_keycontext != KeyContext::Any)
|
||||
return;
|
||||
|
||||
// Automatic key context
|
||||
switch (action) {
|
||||
case KeyAction::None:
|
||||
m_keycontext = KeyContext::Any;
|
||||
@ -233,6 +243,7 @@ Key::Key(KeyAction action)
|
||||
case KeyAction::CopySelection:
|
||||
case KeyAction::SnapToGrid:
|
||||
case KeyAction::LockAxis:
|
||||
case KeyAction::FineControl:
|
||||
m_keycontext = KeyContext::TranslatingSelection;
|
||||
break;
|
||||
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_useUsers(false)
|
||||
, m_keycontext(KeyContext::MouseWheel)
|
||||
@ -395,7 +406,8 @@ std::string Key::triggerString() const
|
||||
return text;
|
||||
}
|
||||
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:
|
||||
return get_user_friendly_string_for_wheelaction(m_wheelAction);
|
||||
}
|
||||
@ -570,9 +582,16 @@ void KeyboardShortcuts::importFile(TiXmlElement* rootElement, KeySource source)
|
||||
if (action_id) {
|
||||
KeyAction action = base::convert_to<KeyAction, std::string>(action_id);
|
||||
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) {
|
||||
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);
|
||||
|
||||
if (!removed)
|
||||
@ -703,6 +722,9 @@ void KeyboardShortcuts::exportAccel(TiXmlElement& parent, const Key* key, const
|
||||
case KeyType::Action:
|
||||
elem.SetAttribute("action",
|
||||
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;
|
||||
|
||||
case KeyType::WheelAction:
|
||||
@ -773,16 +795,18 @@ KeyPtr KeyboardShortcuts::quicktool(tools::Tool* tool)
|
||||
return key;
|
||||
}
|
||||
|
||||
KeyPtr KeyboardShortcuts::action(KeyAction action)
|
||||
KeyPtr KeyboardShortcuts::action(KeyAction action,
|
||||
KeyContext keyContext)
|
||||
{
|
||||
for (KeyPtr& key : m_keys) {
|
||||
if (key->type() == KeyType::Action &&
|
||||
key->action() == action) {
|
||||
key->action() == action &&
|
||||
key->keycontext() == keyContext) {
|
||||
return key;
|
||||
}
|
||||
}
|
||||
|
||||
KeyPtr key = std::make_shared<Key>(action);
|
||||
KeyPtr key = std::make_shared<Key>(action, keyContext);
|
||||
m_keys.push_back(key);
|
||||
return key;
|
||||
}
|
||||
|
@ -1,4 +1,5 @@
|
||||
// Aseprite
|
||||
// Copyright (C) 2020 Igara Studio S.A.
|
||||
// Copyright (C) 2001-2018 David Capello
|
||||
//
|
||||
// This program is distributed under the terms of
|
||||
@ -43,11 +44,13 @@ namespace app {
|
||||
void reset();
|
||||
|
||||
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 quicktool(tools::Tool* tool);
|
||||
KeyPtr action(KeyAction action);
|
||||
KeyPtr wheelAction(WheelAction action);
|
||||
KeyPtr action(const KeyAction action,
|
||||
const KeyContext keyContext = KeyContext::Any);
|
||||
KeyPtr wheelAction(const WheelAction action);
|
||||
|
||||
void disableAccel(const ui::Accelerator& accel,
|
||||
const KeyContext keyContext,
|
||||
@ -88,9 +91,11 @@ namespace app {
|
||||
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(
|
||||
str, KeyboardShortcuts::instance()->action(keyAction).get());
|
||||
str, KeyboardShortcuts::instance()->action(keyAction, keyContext).get());
|
||||
}
|
||||
|
||||
} // namespace app
|
||||
|
@ -157,6 +157,11 @@ void Mask::invert()
|
||||
|
||||
void Mask::replace(const gfx::Rect& bounds)
|
||||
{
|
||||
if (bounds.isEmpty()) {
|
||||
clear();
|
||||
return;
|
||||
}
|
||||
|
||||
m_bounds = bounds;
|
||||
|
||||
m_bitmap.reset(Image::create(IMAGE_BITMAP, bounds.w, bounds.h, m_buffer));
|
||||
|
@ -423,6 +423,9 @@ bool Entry::onProcessMessage(Message* msg)
|
||||
return true;
|
||||
|
||||
case kDoubleClickMessage:
|
||||
if (!hasFocus())
|
||||
requestFocus();
|
||||
|
||||
forwardWord();
|
||||
m_select = m_caret;
|
||||
backwardWord();
|
||||
|
Loading…
x
Reference in New Issue
Block a user