1 // Aseprite
2 // Copyright (C) 2001-2018 David Capello
3 //
4 // This program is distributed under the terms of
5 // the End-User License Agreement for Aseprite.
6
7 #ifdef HAVE_CONFIG_H
8 #include "config.h"
9 #endif
10
11 #include "app/ui/editor/moving_pixels_state.h"
12
13 #include "app/app.h"
14 #include "app/color_utils.h"
15 #include "app/commands/cmd_flip.h"
16 #include "app/commands/cmd_move_mask.h"
17 #include "app/commands/cmd_rotate.h"
18 #include "app/commands/command.h"
19 #include "app/commands/commands.h"
20 #include "app/console.h"
21 #include "app/modules/gui.h"
22 #include "app/pref/preferences.h"
23 #include "app/tools/ink.h"
24 #include "app/tools/tool.h"
25 #include "app/ui/context_bar.h"
26 #include "app/ui/editor/editor.h"
27 #include "app/ui/editor/editor_customization_delegate.h"
28 #include "app/ui/editor/pixels_movement.h"
29 #include "app/ui/editor/standby_state.h"
30 #include "app/ui/editor/transform_handles.h"
31 #include "app/ui/keyboard_shortcuts.h"
32 #include "app/ui/main_window.h"
33 #include "app/ui/status_bar.h"
34 #include "app/ui_context.h"
35 #include "app/util/clipboard.h"
36 #include "base/bind.h"
37 #include "base/pi.h"
38 #include "base/unique_ptr.h"
39 #include "doc/algorithm/flip_image.h"
40 #include "doc/mask.h"
41 #include "doc/sprite.h"
42 #include "gfx/rect.h"
43 #include "ui/manager.h"
44 #include "ui/message.h"
45 #include "ui/system.h"
46 #include "ui/view.h"
47
48 #include <cstring>
49
50 namespace app {
51
52 using namespace ui;
53
MovingPixelsState(Editor * editor,MouseMessage * msg,PixelsMovementPtr pixelsMovement,HandleType handle)54 MovingPixelsState::MovingPixelsState(Editor* editor, MouseMessage* msg, PixelsMovementPtr pixelsMovement, HandleType handle)
55 : m_pixelsMovement(pixelsMovement)
56 , m_editor(editor)
57 , m_observingEditor(false)
58 , m_discarded(false)
59 {
60 // MovingPixelsState needs a selection tool to avoid problems
61 // sharing the extra cel between the drawing cursor preview and the
62 // pixels movement/transformation preview.
63 //ASSERT(!editor->getCurrentEditorInk()->isSelection());
64
65 UIContext* context = UIContext::instance();
66
67 if (handle != NoHandle) {
68 gfx::Point pt = editor->screenToEditor(msg->position());
69 m_pixelsMovement->catchImage(pt, handle);
70
71 editor->captureMouse();
72 }
73
74 // Setup transparent mode/mask color
75 if (Preferences::instance().selection.autoOpaque()) {
76 Preferences::instance().selection.opaque(
77 editor->layer()->isBackground());
78 }
79 onTransparentColorChange();
80
81 // Hook BeforeCommandExecution signal so we know if the user wants
82 // to execute other command, so we can drop pixels.
83 m_ctxConn =
84 context->BeforeCommandExecution.connect(&MovingPixelsState::onBeforeCommandExecution, this);
85
86 // Listen to any change to the transparent color from the ContextBar.
87 m_opaqueConn =
88 Preferences::instance().selection.opaque.AfterChange.connect(
89 base::Bind<void>(&MovingPixelsState::onTransparentColorChange, this));
90 m_transparentConn =
91 Preferences::instance().selection.transparentColor.AfterChange.connect(
92 base::Bind<void>(&MovingPixelsState::onTransparentColorChange, this));
93
94 // Add the current editor as filter for key message of the manager
95 // so we can catch the Enter key, and avoid to execute the
96 // PlayAnimation command.
97 m_editor->manager()->addMessageFilter(kKeyDownMessage, m_editor);
98 m_editor->manager()->addMessageFilter(kKeyUpMessage, m_editor);
99 m_editor->add_observer(this);
100 m_observingEditor = true;
101
102 ContextBar* contextBar = App::instance()->contextBar();
103 contextBar->updateForMovingPixels();
104 contextBar->add_observer(this);
105 }
106
~MovingPixelsState()107 MovingPixelsState::~MovingPixelsState()
108 {
109 ContextBar* contextBar = App::instance()->contextBar();
110 contextBar->remove_observer(this);
111 contextBar->updateForActiveTool();
112
113 removePixelsMovement();
114 removeAsEditorObserver();
115
116 m_editor->manager()->removeMessageFilter(kKeyDownMessage, m_editor);
117 m_editor->manager()->removeMessageFilter(kKeyUpMessage, m_editor);
118
119 m_editor->document()->generateMaskBoundaries();
120 }
121
translate(const gfx::Point & delta)122 void MovingPixelsState::translate(const gfx::Point& delta)
123 {
124 if (m_pixelsMovement->isDragging())
125 m_pixelsMovement->dropImageTemporarily();
126
127 m_pixelsMovement->catchImageAgain(gfx::Point(0, 0), MovePixelsHandle);
128 m_pixelsMovement->moveImage(delta, PixelsMovement::NormalMovement);
129 m_pixelsMovement->dropImageTemporarily();
130 }
131
rotate(double angle)132 void MovingPixelsState::rotate(double angle)
133 {
134 m_pixelsMovement->rotate(angle);
135 }
136
flip(doc::algorithm::FlipType flipType)137 void MovingPixelsState::flip(doc::algorithm::FlipType flipType)
138 {
139 m_pixelsMovement->flipImage(flipType);
140 }
141
onEnterState(Editor * editor)142 void MovingPixelsState::onEnterState(Editor* editor)
143 {
144 StandbyState::onEnterState(editor);
145
146 update_screen_for_document(editor->document());
147 }
148
onLeaveState(Editor * editor,EditorState * newState)149 EditorState::LeaveAction MovingPixelsState::onLeaveState(Editor* editor, EditorState* newState)
150 {
151 TRACE("MOVPIXS: onLeaveState\n");
152
153 ASSERT(m_pixelsMovement);
154 ASSERT(editor == m_editor);
155
156 // If we are changing to another state, we've to drop the image.
157 if (m_pixelsMovement->isDragging())
158 m_pixelsMovement->dropImageTemporarily();
159
160 // Drop pixels if we are changing to a non-temporary state (a
161 // temporary state is something like ScrollingState).
162 if (!newState || !newState->isTemporalState()) {
163 if (!m_discarded) {
164 try {
165 m_pixelsMovement->dropImage();
166 }
167 catch (const LockedDocException& ex) {
168 // This is one of the worst possible scenarios. We want to
169 // drop pixels because we're leaving this state (e.g. the user
170 // changed the current frame/layer, so we came from
171 // onBeforeFrameChanged) and we weren't able to drop those
172 // pixels.
173 //
174 // TODO this problem should be caught before we reach this
175 // state, or this problem should cancel the frame/layer
176 // change.
177 Console::showException(ex);
178 }
179 }
180
181 editor->document()->resetTransformation();
182
183 removePixelsMovement();
184
185 editor->releaseMouse();
186
187 // Redraw the document without the transformation handles.
188 editor->document()->notifyGeneralUpdate();
189
190 return DiscardState;
191 }
192 else {
193 editor->releaseMouse();
194 return KeepState;
195 }
196 }
197
onActiveToolChange(Editor * editor,tools::Tool * tool)198 void MovingPixelsState::onActiveToolChange(Editor* editor, tools::Tool* tool)
199 {
200 ASSERT(m_pixelsMovement);
201 ASSERT(editor == m_editor);
202
203 // If the user changed the tool when he/she is moving pixels,
204 // we have to drop the pixels only if the new tool is not selection...
205 if (m_pixelsMovement) {
206 // We don't want to drop pixels in case the user change the tool
207 // for scrolling/zooming/picking colors.
208 if ((!tool->getInk(0)->isSelection() ||
209 !tool->getInk(1)->isSelection()) &&
210 (!tool->getInk(0)->isScrollMovement() ||
211 !tool->getInk(1)->isScrollMovement()) &&
212 (!tool->getInk(0)->isZoom() ||
213 !tool->getInk(1)->isZoom()) &&
214 (!tool->getInk(0)->isEyedropper() ||
215 !tool->getInk(1)->isEyedropper())) {
216 // We have to drop pixels
217 dropPixels();
218 }
219 // If we've temporarily gone to a non-selection tool and now we're
220 // back, we've just to update the context bar to show the "moving
221 // pixels" controls (e.g. OK/Cancel movement buttons).
222 else if (tool->getInk(0)->isSelection() ||
223 tool->getInk(1)->isSelection()) {
224 ContextBar* contextBar = App::instance()->contextBar();
225 contextBar->updateForMovingPixels();
226 }
227 }
228 }
229
onMouseDown(Editor * editor,MouseMessage * msg)230 bool MovingPixelsState::onMouseDown(Editor* editor, MouseMessage* msg)
231 {
232 ASSERT(m_pixelsMovement);
233 ASSERT(editor == m_editor);
234
235 // Set this editor as the active one and setup the ContextBar for
236 // moving pixels. This is needed in case that the user is working
237 // with a couple of Editors, in one is moving pixels and the other
238 // one not.
239 UIContext* ctx = UIContext::instance();
240 ctx->setActiveView(editor->getDocView());
241
242 ContextBar* contextBar = App::instance()->contextBar();
243 contextBar->updateForMovingPixels();
244
245 // Start scroll loop
246 if (editor->checkForScroll(msg) ||
247 editor->checkForZoom(msg))
248 return true;
249
250 // Call the eyedropper command
251 tools::Ink* clickedInk = editor->getCurrentEditorInk();
252 if (clickedInk->isEyedropper()) {
253 callEyedropper(editor, msg);
254 return true;
255 }
256
257 Decorator* decorator = static_cast<Decorator*>(editor->decorator());
258 Doc* document = editor->document();
259
260 // Transform selected pixels
261 if (document->isMaskVisible() &&
262 decorator->getTransformHandles(editor) &&
263 (!Preferences::instance().selection.modifiersDisableHandles() ||
264 msg->modifiers() == kKeyNoneModifier)) {
265 TransformHandles* transfHandles = decorator->getTransformHandles(editor);
266
267 // Get the handle covered by the mouse.
268 HandleType handle = transfHandles->getHandleAtPoint(editor,
269 msg->position(),
270 getTransformation(editor));
271
272 if (handle != NoHandle) {
273 // Re-catch the image
274 m_pixelsMovement->catchImageAgain(
275 editor->screenToEditor(msg->position()), handle);
276
277 editor->captureMouse();
278 return true;
279 }
280 }
281
282 // Start "moving pixels" loop. Here we check only for left-click as
283 // right-click can be used to deselect/subtract selection, so we
284 // should drop the selection in this later case.
285 if (editor->isInsideSelection() && msg->left()) {
286 // In case that the user is pressing the copy-selection keyboard shortcut.
287 EditorCustomizationDelegate* customization = editor->getCustomizationDelegate();
288 if ((customization) &&
289 int(customization->getPressedKeyAction(KeyContext::TranslatingSelection) & KeyAction::CopySelection)) {
290 // Stamp the pixels to create the copy.
291 m_pixelsMovement->stampImage();
292 }
293
294 // Re-catch the image
295 m_pixelsMovement->catchImageAgain(
296 editor->screenToEditor(msg->position()), MovePixelsHandle);
297
298 editor->captureMouse();
299 return true;
300 }
301 // End "moving pixels" loop
302 else {
303 // Drop pixels (e.g. to start drawing)
304 dropPixels();
305 }
306
307 // Use StandbyState implementation
308 return StandbyState::onMouseDown(editor, msg);
309 }
310
onMouseUp(Editor * editor,MouseMessage * msg)311 bool MovingPixelsState::onMouseUp(Editor* editor, MouseMessage* msg)
312 {
313 ASSERT(m_pixelsMovement);
314 ASSERT(editor == m_editor);
315
316 // Drop the image temporarily in this location (where the user releases the mouse)
317 m_pixelsMovement->dropImageTemporarily();
318
319 // Redraw the new pivot location.
320 editor->invalidate();
321
322 editor->releaseMouse();
323 return true;
324 }
325
onMouseMove(Editor * editor,MouseMessage * msg)326 bool MovingPixelsState::onMouseMove(Editor* editor, MouseMessage* msg)
327 {
328 ASSERT(m_pixelsMovement);
329 ASSERT(editor == m_editor);
330
331 // If there is a button pressed
332 if (m_pixelsMovement->isDragging()) {
333 // Auto-scroll
334 gfx::Point mousePos = editor->autoScroll(msg, AutoScroll::MouseDir);
335
336 // Get the position of the mouse in the sprite
337 gfx::Point spritePos = editor->screenToEditor(mousePos);
338
339 // Get the customization for the pixels movement (snap to grid, angle snap, etc.).
340 KeyContext keyContext = KeyContext::Normal;
341 switch (m_pixelsMovement->handle()) {
342 case MovePixelsHandle:
343 keyContext = KeyContext::TranslatingSelection;
344 break;
345 case ScaleNWHandle:
346 case ScaleNHandle:
347 case ScaleNEHandle:
348 case ScaleWHandle:
349 case ScaleEHandle:
350 case ScaleSWHandle:
351 case ScaleSHandle:
352 case ScaleSEHandle:
353 keyContext = KeyContext::ScalingSelection;
354 break;
355 case RotateNWHandle:
356 case RotateNHandle:
357 case RotateNEHandle:
358 case RotateWHandle:
359 case RotateEHandle:
360 case RotateSWHandle:
361 case RotateSHandle:
362 case RotateSEHandle:
363 keyContext = KeyContext::RotatingSelection;
364 break;
365 }
366
367 PixelsMovement::MoveModifier moveModifier = PixelsMovement::NormalMovement;
368 KeyAction action = editor->getCustomizationDelegate()
369 ->getPressedKeyAction(keyContext);
370
371 if (int(action & KeyAction::SnapToGrid))
372 moveModifier |= PixelsMovement::SnapToGridMovement;
373
374 if (int(action & KeyAction::AngleSnap))
375 moveModifier |= PixelsMovement::AngleSnapMovement;
376
377 if (int(action & KeyAction::MaintainAspectRatio))
378 moveModifier |= PixelsMovement::MaintainAspectRatioMovement;
379
380 if (int(action & KeyAction::ScaleFromCenter))
381 moveModifier |= PixelsMovement::ScaleFromPivot;
382
383 if (int(action & KeyAction::LockAxis))
384 moveModifier |= PixelsMovement::LockAxisMovement;
385
386 // Invalidate handles
387 Decorator* decorator = static_cast<Decorator*>(editor->decorator());
388 TransformHandles* transfHandles = decorator->getTransformHandles(editor);
389 transfHandles->invalidateHandles(editor, m_pixelsMovement->getTransformation());
390
391 // Drag the image to that position
392 m_pixelsMovement->moveImage(spritePos, moveModifier);
393
394 editor->updateStatusBar();
395 return true;
396 }
397
398 // Use StandbyState implementation
399 return StandbyState::onMouseMove(editor, msg);
400 }
401
onSetCursor(Editor * editor,const gfx::Point & mouseScreenPos)402 bool MovingPixelsState::onSetCursor(Editor* editor, const gfx::Point& mouseScreenPos)
403 {
404 ASSERT(m_pixelsMovement);
405 ASSERT(editor == m_editor);
406
407 // Move selection
408 if (m_pixelsMovement->isDragging()) {
409 editor->showMouseCursor(kMoveCursor);
410 return true;
411 }
412
413 // Use StandbyState implementation
414 return StandbyState::onSetCursor(editor, mouseScreenPos);
415 }
416
onKeyDown(Editor * editor,KeyMessage * msg)417 bool MovingPixelsState::onKeyDown(Editor* editor, KeyMessage* msg)
418 {
419 ASSERT(m_pixelsMovement);
420 if (!isActiveEditor())
421 return false;
422 ASSERT(editor == m_editor);
423
424 if (msg->scancode() == kKeyEnter || // TODO make this key customizable
425 msg->scancode() == kKeyEnterPad ||
426 msg->scancode() == kKeyEsc) {
427 dropPixels();
428
429 // The escape key drop pixels and deselect the mask.
430 if (msg->scancode() == kKeyEsc) { // TODO make this key customizable
431 Command* cmd = Commands::instance()->byId(CommandId::DeselectMask());
432 UIContext::instance()->executeCommand(cmd);
433 }
434
435 return true;
436 }
437
438 // Use StandbyState implementation
439 return StandbyState::onKeyDown(editor, msg);
440 }
441
onKeyUp(Editor * editor,KeyMessage * msg)442 bool MovingPixelsState::onKeyUp(Editor* editor, KeyMessage* msg)
443 {
444 ASSERT(m_pixelsMovement);
445 if (!isActiveEditor())
446 return false;
447 ASSERT(editor == m_editor);
448
449 // Use StandbyState implementation
450 return StandbyState::onKeyUp(editor, msg);
451 }
452
onUpdateStatusBar(Editor * editor)453 bool MovingPixelsState::onUpdateStatusBar(Editor* editor)
454 {
455 ASSERT(m_pixelsMovement);
456 ASSERT(editor == m_editor);
457
458 const Transformation& transform(getTransformation(editor));
459 gfx::Size imageSize = m_pixelsMovement->getInitialImageSize();
460
461 StatusBar::instance()->setStatusText
462 (100, ":pos: %d %d :size: %3d %3d :selsize: %d %d [%.02f%% %.02f%%] :angle: %.1f",
463 int(transform.bounds().x),
464 int(transform.bounds().y),
465 imageSize.w,
466 imageSize.h,
467 int(transform.bounds().w),
468 int(transform.bounds().h),
469 (double)transform.bounds().w*100.0/imageSize.w,
470 (double)transform.bounds().h*100.0/imageSize.h,
471 180.0 * transform.angle() / PI);
472
473 return true;
474 }
475
acceptQuickTool(tools::Tool * tool)476 bool MovingPixelsState::acceptQuickTool(tools::Tool* tool)
477 {
478 return
479 (!m_pixelsMovement ||
480 tool->getInk(0)->isSelection() ||
481 tool->getInk(0)->isEyedropper() ||
482 tool->getInk(0)->isScrollMovement() ||
483 tool->getInk(0)->isZoom());
484 }
485
486 // Before executing any command, we drop the pixels (go back to standby).
onBeforeCommandExecution(CommandExecutionEvent & ev)487 void MovingPixelsState::onBeforeCommandExecution(CommandExecutionEvent& ev)
488 {
489 Command* command = ev.command();
490
491 TRACE("MOVPIXS: onBeforeCommandExecution %s\n", command->id().c_str());
492
493 // If the command is for other editor, we don't drop pixels.
494 if (!isActiveEditor())
495 return;
496
497 // We don't need to drop the pixels if a MoveMaskCommand of Content is executed.
498 if (MoveMaskCommand* moveMaskCmd = dynamic_cast<MoveMaskCommand*>(ev.command())) {
499 if (moveMaskCmd->getTarget() == MoveMaskCommand::Content) {
500 // Do not drop pixels
501 return;
502 }
503 }
504 // Don't drop pixels if the user zooms/scrolls/picks a color
505 // using commands.
506 else if ((command->id() == CommandId::Zoom()) ||
507 (command->id() == CommandId::Scroll()) ||
508 (command->id() == CommandId::Eyedropper()) ||
509 // DiscardBrush is used by Eyedropper command
510 (command->id() == CommandId::DiscardBrush())) {
511 // Do not drop pixels
512 return;
513 }
514 // Intercept the "Cut" or "Copy" command to handle them locally
515 // with the current m_pixelsMovement data.
516 else if (command->id() == CommandId::Cut() ||
517 command->id() == CommandId::Copy() ||
518 command->id() == CommandId::Clear()) {
519 // Copy the floating image to the clipboard on Cut/Copy.
520 if (command->id() != CommandId::Clear()) {
521 Doc* document = m_editor->document();
522 base::UniquePtr<Image> floatingImage;
523 base::UniquePtr<Mask> floatingMask;
524 m_pixelsMovement->getDraggedImageCopy(floatingImage, floatingMask);
525
526 clipboard::copy_image(floatingImage.get(),
527 floatingMask.get(),
528 document->sprite()->palette(m_editor->frame()));
529 }
530
531 // Clear floating pixels on Cut/Clear.
532 if (command->id() != CommandId::Copy()) {
533 m_pixelsMovement->trim();
534
535 // Should we keep the mask after an Edit > Clear command?
536 auto keepMask = PixelsMovement::DontKeepMask;
537 if (command->id() == CommandId::Clear() &&
538 Preferences::instance().selection.keepSelectionAfterClear()) {
539 keepMask = PixelsMovement::KeepMask;
540 }
541
542 // Discard the dragged image.
543 m_pixelsMovement->discardImage(PixelsMovement::CommitChanges, keepMask);
544 m_discarded = true;
545
546 // Quit from MovingPixelsState, back to standby.
547 m_editor->backToPreviousState();
548 }
549
550 // Cancel the command, we've simulated it.
551 ev.cancel();
552 return;
553 }
554 // Flip Horizontally/Vertically commands are handled manually to
555 // avoid dropping the floating region of pixels.
556 else if (command->id() == CommandId::Flip()) {
557 if (FlipCommand* flipCommand = dynamic_cast<FlipCommand*>(command)) {
558 m_pixelsMovement->flipImage(flipCommand->getFlipType());
559
560 ev.cancel();
561 return;
562 }
563 }
564 // Rotate is quite simple, we can add the angle to the current transformation.
565 else if (command->id() == CommandId::Rotate()) {
566 if (RotateCommand* rotate = dynamic_cast<RotateCommand*>(command)) {
567 if (rotate->flipMask()) {
568 m_pixelsMovement->rotate(rotate->angle());
569
570 ev.cancel();
571 return;
572 }
573 }
574 }
575
576 if (m_pixelsMovement)
577 dropPixels();
578 }
579
onDestroyEditor(Editor * editor)580 void MovingPixelsState::onDestroyEditor(Editor* editor)
581 {
582 // TODO we should call ~MovingPixelsState(), we should delete the
583 // whole "m_statesHistory" when an editor is deleted.
584 removeAsEditorObserver();
585 }
586
onBeforeFrameChanged(Editor * editor)587 void MovingPixelsState::onBeforeFrameChanged(Editor* editor)
588 {
589 if (!isActiveDocument())
590 return;
591
592 if (m_pixelsMovement)
593 dropPixels();
594 }
595
onBeforeLayerChanged(Editor * editor)596 void MovingPixelsState::onBeforeLayerChanged(Editor* editor)
597 {
598 if (!isActiveDocument())
599 return;
600
601 if (m_pixelsMovement)
602 dropPixels();
603 }
604
onTransparentColorChange()605 void MovingPixelsState::onTransparentColorChange()
606 {
607 ASSERT(m_pixelsMovement);
608
609 bool opaque = Preferences::instance().selection.opaque();
610 setTransparentColor(
611 opaque,
612 opaque ?
613 app::Color::fromMask():
614 Preferences::instance().selection.transparentColor());
615 }
616
onDropPixels(ContextBarObserver::DropAction action)617 void MovingPixelsState::onDropPixels(ContextBarObserver::DropAction action)
618 {
619 if (!isActiveEditor())
620 return;
621
622 switch (action) {
623
624 case ContextBarObserver::DropPixels:
625 dropPixels();
626 break;
627
628 case ContextBarObserver::CancelDrag:
629 m_pixelsMovement->discardImage(PixelsMovement::DontCommitChanges);
630 m_discarded = true;
631
632 // Quit from MovingPixelsState, back to standby.
633 m_editor->backToPreviousState();
634 break;
635 }
636 }
637
setTransparentColor(bool opaque,const app::Color & color)638 void MovingPixelsState::setTransparentColor(bool opaque, const app::Color& color)
639 {
640 ASSERT(m_pixelsMovement);
641
642 Layer* layer = m_editor->layer();
643 ASSERT(layer);
644
645 try {
646 m_pixelsMovement->setMaskColor(
647 opaque, color_utils::color_for_target_mask(color, ColorTarget(layer)));
648 }
649 catch (const LockedDocException& ex) {
650 Console::showException(ex);
651 }
652 }
653
dropPixels()654 void MovingPixelsState::dropPixels()
655 {
656 TRACE("MOVPIXS: dropPixels\n");
657
658 // Just change to default state (StandbyState generally). We'll
659 // receive an onLeaveState() event after this call.
660 m_editor->backToPreviousState();
661 }
662
getTransformation(Editor * editor)663 Transformation MovingPixelsState::getTransformation(Editor* editor)
664 {
665 // m_pixelsMovement can be null in the final onMouseDown(), after we
666 // called dropPixels() and we're just going to the previous state.
667 if (m_pixelsMovement)
668 return m_pixelsMovement->getTransformation();
669 else
670 return StandbyState::getTransformation(editor);
671 }
672
isActiveDocument() const673 bool MovingPixelsState::isActiveDocument() const
674 {
675 Doc* doc = UIContext::instance()->activeDocument();
676 return (m_editor->document() == doc);
677 }
678
isActiveEditor() const679 bool MovingPixelsState::isActiveEditor() const
680 {
681 Editor* targetEditor = UIContext::instance()->activeEditor();
682 return (targetEditor == m_editor);
683 }
684
removeAsEditorObserver()685 void MovingPixelsState::removeAsEditorObserver()
686 {
687 if (m_observingEditor) {
688 m_observingEditor = false;
689 m_editor->remove_observer(this);
690 }
691 }
692
removePixelsMovement()693 void MovingPixelsState::removePixelsMovement()
694 {
695 m_pixelsMovement.reset(nullptr);
696 m_ctxConn.disconnect();
697 m_opaqueConn.disconnect();
698 m_transparentConn.disconnect();
699 }
700
701 } // namespace app
702