| 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 | |
| 26 | namespace app { |
| 27 | |
| 28 | using namespace ui; |
| 29 | |
| 30 | MovingSliceState::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 | |
| 54 | bool 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 | |
| 73 | bool 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 | |
| 172 | bool 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 | |
| 203 | MovingSliceState::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 | |
| 216 | gfx::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 | |