1// Aseprite
2// Copyright (C) 2019-2020 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_slice_state.h"
13
14#include "app/cmd/set_slice_key.h"
15#include "app/context_access.h"
16#include "app/tx.h"
17#include "app/ui/editor/editor.h"
18#include "app/ui/status_bar.h"
19#include "app/ui_context.h"
20#include "doc/slice.h"
21#include "ui/message.h"
22
23#include <algorithm>
24#include <cmath>
25
26namespace app {
27
28using namespace ui;
29
30MovingSliceState::MovingSliceState(Editor* editor,
31 MouseMessage* msg,
32 const EditorHit& hit,
33 const doc::SelectedObjects& selectedSlices)
34 : m_frame(editor->frame())
35 , m_hit(hit)
36 , m_items(std::max<std::size_t>(1, selectedSlices.size()))
37{
38 m_mouseStart = editor->screenToEditor(msg->position());
39
40 if (selectedSlices.empty()) {
41 m_items[0] = getItemForSlice(m_hit.slice());
42 }
43 else {
44 int i = 0;
45 for (Slice* slice : selectedSlices.iterateAs<Slice>()) {
46 ASSERT(slice);
47 m_items[i++] = getItemForSlice(slice);
48 }
49 }
50
51 editor->captureMouse();
52}
53
54bool MovingSliceState::onMouseUp(Editor* editor, MouseMessage* msg)
55{
56 {
57 ContextWriter writer(UIContext::instance(), 1000);
58 Tx tx(writer.context(), "Slice Movement", ModifyDocument);
59
60 for (const auto& item : m_items) {
61 item.slice->insert(m_frame, item.oldKey);
62 tx(new cmd::SetSliceKey(item.slice, m_frame, item.newKey));
63 }
64
65 tx.commit();
66 }
67
68 editor->backToPreviousState();
69 editor->releaseMouse();
70 return true;
71}
72
73bool MovingSliceState::onMouseMove(Editor* editor, MouseMessage* msg)
74{
75 gfx::Point newCursorPos = editor->screenToEditor(msg->position());
76 gfx::Point delta = newCursorPos - m_mouseStart;
77 gfx::Rect totalBounds = selectedSlicesBounds();
78
79 ASSERT(totalBounds.w > 0);
80 ASSERT(totalBounds.h > 0);
81
82 for (auto& item : m_items) {
83 auto& key = item.newKey;
84 key = item.oldKey;
85
86 gfx::Rect rc =
87 (m_hit.type() == EditorHit::SliceCenter ? key.center():
88 key.bounds());
89
90 // Move slice
91 if (m_hit.border() == (CENTER | MIDDLE)) {
92 rc.x += delta.x;
93 rc.y += delta.y;
94 }
95 // Move/resize 9-slices center
96 else if (m_hit.type() == EditorHit::SliceCenter) {
97 if (m_hit.border() & LEFT) {
98 rc.x += delta.x;
99 rc.w -= delta.x;
100 if (rc.w < 1) {
101 rc.x += rc.w-1;
102 rc.w = 1;
103 }
104 }
105 if (m_hit.border() & TOP) {
106 rc.y += delta.y;
107 rc.h -= delta.y;
108 if (rc.h < 1) {
109 rc.y += rc.h-1;
110 rc.h = 1;
111 }
112 }
113 if (m_hit.border() & RIGHT) {
114 rc.w += delta.x;
115 if (rc.w < 1)
116 rc.w = 1;
117 }
118 if (m_hit.border() & BOTTOM) {
119 rc.h += delta.y;
120 if (rc.h < 1)
121 rc.h = 1;
122 }
123 }
124 // Move/resize bounds
125 else {
126 if (m_hit.border() & LEFT) {
127 rc.x += delta.x * (totalBounds.x2() - rc.x) / totalBounds.w;
128 rc.w -= delta.x * rc.w / totalBounds.w;
129 if (rc.w < 1) {
130 rc.x += rc.w-1;
131 rc.w = 1;
132 }
133 }
134 if (m_hit.border() & TOP) {
135 rc.y += delta.y * (totalBounds.y2() - rc.y) / totalBounds.h;
136 rc.h -= delta.y * rc.h / totalBounds.h;
137 if (rc.h < 1) {
138 rc.y += rc.h-1;
139 rc.h = 1;
140 }
141 }
142 if (m_hit.border() & RIGHT) {
143 rc.x += delta.x * (rc.x - totalBounds.x) / totalBounds.w;
144 rc.w += delta.x * rc.w / totalBounds.w;
145 if (rc.w < 1)
146 rc.w = 1;
147 }
148 if (m_hit.border() & BOTTOM) {
149 rc.y += delta.y * (rc.y - totalBounds.y) / totalBounds.h;
150 rc.h += delta.y * rc.h / totalBounds.h;
151 if (rc.h < 1)
152 rc.h = 1;
153 }
154 }
155
156 if (m_hit.type() == EditorHit::SliceCenter)
157 key.setCenter(rc);
158 else
159 key.setBounds(rc);
160
161 // Update the slice key
162 item.slice->insert(m_frame, key);
163 }
164
165 // Redraw the editor.
166 editor->invalidate();
167
168 // Use StandbyState implementation
169 return StandbyState::onMouseMove(editor, msg);
170}
171
172bool MovingSliceState::onSetCursor(Editor* editor, const gfx::Point& mouseScreenPos)
173{
174 switch (m_hit.border()) {
175 case TOP | LEFT:
176 editor->showMouseCursor(kSizeNWCursor);
177 break;
178 case TOP:
179 editor->showMouseCursor(kSizeNCursor);
180 break;
181 case TOP | RIGHT:
182 editor->showMouseCursor(kSizeNECursor);
183 break;
184 case LEFT:
185 editor->showMouseCursor(kSizeWCursor);
186 break;
187 case RIGHT:
188 editor->showMouseCursor(kSizeECursor);
189 break;
190 case BOTTOM | LEFT:
191 editor->showMouseCursor(kSizeSWCursor);
192 break;
193 case BOTTOM:
194 editor->showMouseCursor(kSizeSCursor);
195 break;
196 case BOTTOM | RIGHT:
197 editor->showMouseCursor(kSizeSECursor);
198 break;
199 }
200 return true;
201}
202
203MovingSliceState::Item MovingSliceState::getItemForSlice(doc::Slice* slice)
204{
205 Item item;
206 item.slice = slice;
207
208 auto keyPtr = slice->getByFrame(m_frame);
209 ASSERT(keyPtr);
210 if (keyPtr)
211 item.oldKey = item.newKey = *keyPtr;
212
213 return item;
214}
215
216gfx::Rect MovingSliceState::selectedSlicesBounds() const
217{
218 gfx::Rect bounds;
219 for (auto& item : m_items)
220 bounds |= item.oldKey.bounds();
221 return bounds;
222}
223
224} // namespace app
225