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 | |
28 | namespace app { |
29 | |
30 | using namespace ui; |
31 | |
32 | MovingSelectionState::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 | |
46 | void 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 | |
58 | void MovingSelectionState::onEnterState(Editor* editor) |
59 | { |
60 | StandbyState::onEnterState(editor); |
61 | editor->document()->mask()->freeze(); |
62 | } |
63 | |
64 | EditorState::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 | |
95 | void MovingSelectionState::onBeforePopState(Editor* editor) |
96 | { |
97 | m_ctxConn.disconnect(); |
98 | StandbyState::onBeforePopState(editor); |
99 | } |
100 | |
101 | bool MovingSelectionState::onMouseDown(Editor* editor, MouseMessage* msg) |
102 | { |
103 | // Do nothing |
104 | return true; |
105 | } |
106 | |
107 | bool MovingSelectionState::onMouseUp(Editor* editor, MouseMessage* msg) |
108 | { |
109 | editor->backToPreviousState(); |
110 | editor->releaseMouse(); |
111 | return true; |
112 | } |
113 | |
114 | bool 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 | |
145 | bool 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 | |
153 | bool 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 | |