1// Aseprite
2// Copyright (C) 2019-2022 Igara Studio S.A.
3// Copyright (C) 2017-2018 David Capello
4//
5// This program is distributed under the terms of
6// the End-User License Agreement for Aseprite.
7
8#ifdef HAVE_CONFIG_H
9#include "config.h"
10#endif
11
12#include "app/ui/editor/moving_selection_state.h"
13
14#include "app/cmd/set_mask_position.h"
15#include "app/commands/command.h"
16#include "app/commands/commands.h"
17#include "app/context_access.h"
18#include "app/tx.h"
19#include "app/ui/editor/editor.h"
20#include "app/ui/skin/skin_theme.h"
21#include "app/ui/status_bar.h"
22#include "app/ui_context.h"
23#include "app/ui_context.h"
24#include "doc/mask.h"
25#include "fmt/format.h"
26#include "ui/message.h"
27
28namespace app {
29
30using namespace ui;
31
32MovingSelectionState::MovingSelectionState(Editor* editor, MouseMessage* msg)
33 : m_editor(editor)
34 , m_cursorStart(editor->screenToEditor(msg->position()))
35 , m_selOrigin(editor->document()->mask()->bounds().origin())
36 , m_selectionCanceled(false)
37{
38 editor->captureMouse();
39
40 // Hook BeforeCommandExecution signal so we know if the user wants
41 // to execute other command, so we can unfreeze the document mask.
42 m_ctxConn = UIContext::instance()->BeforeCommandExecution.connect(
43 &MovingSelectionState::onBeforeCommandExecution, this);
44}
45
46void MovingSelectionState::onBeforeCommandExecution(CommandExecutionEvent& ev)
47{
48 if (ev.command()->id() == CommandId::Cancel()) {
49 // We cancel the cancel command to avoid calling the inputChain()->cancel(), which
50 // deselects the selection.
51 ev.cancel();
52 m_selectionCanceled = true;
53 }
54 m_editor->backToPreviousState();
55 m_editor->releaseMouse();
56}
57
58void MovingSelectionState::onEnterState(Editor* editor)
59{
60 StandbyState::onEnterState(editor);
61 editor->document()->mask()->freeze();
62}
63
64EditorState::LeaveAction MovingSelectionState::onLeaveState(Editor* editor, EditorState* newState)
65{
66 Doc* doc = editor->document();
67 Mask* mask = doc->mask();
68 gfx::Point newOrigin = mask->bounds().origin();
69
70 ASSERT(mask->isFrozen());
71
72 // Restore the mask to the original state so we can transform it
73 // with the a undoable transaction.
74 mask->setOrigin(m_selOrigin.x,
75 m_selOrigin.y);
76 mask->unfreeze();
77
78 if (m_selectionCanceled) {
79 doc->resetTransformation();
80 doc->generateMaskBoundaries();
81 }
82 else {
83 {
84 ContextWriter writer(UIContext::instance(), 1000);
85 Tx tx(writer.context(), "Move Selection Edges", DoesntModifyDocument);
86 tx(new cmd::SetMaskPosition(doc, newOrigin));
87 tx.commit();
88 }
89 doc->resetTransformation();
90 }
91 doc->notifyGeneralUpdate();
92 return StandbyState::onLeaveState(editor, newState);
93}
94
95void MovingSelectionState::onBeforePopState(Editor* editor)
96{
97 m_ctxConn.disconnect();
98 StandbyState::onBeforePopState(editor);
99}
100
101bool MovingSelectionState::onMouseDown(Editor* editor, MouseMessage* msg)
102{
103 // Do nothing
104 return true;
105}
106
107bool MovingSelectionState::onMouseUp(Editor* editor, MouseMessage* msg)
108{
109 editor->backToPreviousState();
110 editor->releaseMouse();
111 return true;
112}
113
114bool MovingSelectionState::onMouseMove(Editor* editor, MouseMessage* msg)
115{
116 const gfx::Point mousePos = editor->autoScroll(msg, AutoScroll::MouseDir);
117 const gfx::Point newCursorPos = editor->screenToEditor(mousePos);
118 m_delta = newCursorPos - m_cursorStart;
119 const gfx::Point newMaskOrigin = m_selOrigin + m_delta;
120 const gfx::Point oldMaskOrigin = editor->document()->mask()->bounds().origin();
121
122 ASSERT(editor->document()->mask()->isFrozen());
123
124 if (oldMaskOrigin != newMaskOrigin) {
125 editor->document()->mask()->setOrigin(newMaskOrigin.x,
126 newMaskOrigin.y);
127
128 if (editor->document()->hasMaskBoundaries()) {
129 MaskBoundaries& boundaries = editor->document()->maskBoundaries();
130 const gfx::Point boundariesDelta = newMaskOrigin - oldMaskOrigin;
131 boundaries.offset(boundariesDelta.x,
132 boundariesDelta.y);
133 }
134 else {
135 ASSERT(false);
136 }
137
138 editor->invalidate();
139 }
140
141 // Use StandbyState implementation
142 return StandbyState::onMouseMove(editor, msg);
143}
144
145bool MovingSelectionState::onSetCursor(Editor* editor, const gfx::Point& mouseScreenPos)
146{
147 auto theme = skin::SkinTheme::get(editor);
148 editor->showMouseCursor(
149 kCustomCursor, theme->cursors.moveSelection());
150 return true;
151}
152
153bool MovingSelectionState::onUpdateStatusBar(Editor* editor)
154{
155 const gfx::Rect bounds = editor->document()->mask()->bounds();
156
157 StatusBar::instance()->setStatusText(
158 100,
159 fmt::format(
160 ":pos: {} {}"
161 " :size: {} {}"
162 " :delta: {} {}",
163 bounds.x, bounds.y,
164 bounds.w, bounds.h,
165 m_delta.x, m_delta.y));
166
167 return true;
168}
169
170} // namespace app
171