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/editor.h"
12 
13 #include "app/app.h"
14 #include "app/color.h"
15 #include "app/color_picker.h"
16 #include "app/color_utils.h"
17 #include "app/commands/commands.h"
18 #include "app/commands/params.h"
19 #include "app/commands/quick_command.h"
20 #include "app/console.h"
21 #include "app/doc_event.h"
22 #include "app/i18n/strings.h"
23 #include "app/ini_file.h"
24 #include "app/modules/editors.h"
25 #include "app/modules/gfx.h"
26 #include "app/modules/gui.h"
27 #include "app/modules/palettes.h"
28 #include "app/pref/preferences.h"
29 #include "app/tools/active_tool.h"
30 #include "app/tools/controller.h"
31 #include "app/tools/ink.h"
32 #include "app/tools/tool.h"
33 #include "app/tools/tool_box.h"
34 #include "app/ui/color_bar.h"
35 #include "app/ui/context_bar.h"
36 #include "app/ui/editor/drawing_state.h"
37 #include "app/ui/editor/editor_customization_delegate.h"
38 #include "app/ui/editor/editor_decorator.h"
39 #include "app/ui/editor/editor_render.h"
40 #include "app/ui/editor/glue.h"
41 #include "app/ui/editor/moving_pixels_state.h"
42 #include "app/ui/editor/pixels_movement.h"
43 #include "app/ui/editor/play_state.h"
44 #include "app/ui/editor/scrolling_state.h"
45 #include "app/ui/editor/standby_state.h"
46 #include "app/ui/editor/zooming_state.h"
47 #include "app/ui/main_window.h"
48 #include "app/ui/skin/skin_theme.h"
49 #include "app/ui/status_bar.h"
50 #include "app/ui/toolbar.h"
51 #include "app/ui_context.h"
52 #include "base/bind.h"
53 #include "base/chrono.h"
54 #include "base/convert_to.h"
55 #include "base/unique_ptr.h"
56 #include "doc/conversion_she.h"
57 #include "doc/doc.h"
58 #include "doc/mask_boundaries.h"
59 #include "doc/slice.h"
60 #include "she/surface.h"
61 #include "she/system.h"
62 #include "ui/ui.h"
63 
64 #include <algorithm>
65 #include <cmath>
66 #include <cstdio>
67 #include <limits>
68 #include <memory>
69 
70 namespace app {
71 
72 using namespace app::skin;
73 using namespace gfx;
74 using namespace ui;
75 using namespace render;
76 
77 // TODO these should be grouped in some kind of "performance counters"
78 static base::Chrono renderChrono;
79 static double renderElapsed = 0.0;
80 
81 class EditorPostRenderImpl : public EditorPostRender {
82 public:
EditorPostRenderImpl(Editor * editor,Graphics * g)83   EditorPostRenderImpl(Editor* editor, Graphics* g)
84     : m_editor(editor)
85     , m_g(g) {
86   }
87 
getEditor()88   Editor* getEditor() override {
89     return m_editor;
90   }
91 
drawLine(gfx::Color color,int x1,int y1,int x2,int y2)92   void drawLine(gfx::Color color, int x1, int y1, int x2, int y2) override {
93     gfx::Point a(x1, y1);
94     gfx::Point b(x2, y2);
95     a = m_editor->editorToScreen(a);
96     b = m_editor->editorToScreen(b);
97     gfx::Rect bounds = m_editor->bounds();
98     a.x -= bounds.x;
99     a.y -= bounds.y;
100     b.x -= bounds.x;
101     b.y -= bounds.y;
102     m_g->drawLine(color, a, b);
103   }
104 
drawRectXor(const gfx::Rect & rc)105   void drawRectXor(const gfx::Rect& rc) override {
106     gfx::Rect rc2 = m_editor->editorToScreen(rc);
107     gfx::Rect bounds = m_editor->bounds();
108     rc2.x -= bounds.x;
109     rc2.y -= bounds.y;
110 
111     m_g->setDrawMode(Graphics::DrawMode::Xor);
112     m_g->drawRect(gfx::rgba(255, 255, 255), rc2);
113     m_g->setDrawMode(Graphics::DrawMode::Solid);
114   }
115 
fillRect(gfx::Color color,const gfx::Rect & rc)116   void fillRect(gfx::Color color, const gfx::Rect& rc) override {
117     gfx::Rect rc2 = m_editor->editorToScreen(rc);
118     gfx::Rect bounds = m_editor->bounds();
119     rc2.x -= bounds.x;
120     rc2.y -= bounds.y;
121     m_g->fillRect(color, rc2);
122   }
123 
124 private:
125   Editor* m_editor;
126   Graphics* m_g;
127 };
128 
129 // static
130 EditorRender* Editor::m_renderEngine = nullptr;
131 
Editor(Doc * document,EditorFlags flags)132 Editor::Editor(Doc* document, EditorFlags flags)
133   : Widget(editor_type())
134   , m_state(new StandbyState())
135   , m_decorator(NULL)
136   , m_document(document)
137   , m_sprite(m_document->sprite())
138   , m_layer(m_sprite->root()->firstLayer())
139   , m_frame(frame_t(0))
140   , m_docPref(Preferences::instance().document(document))
141   , m_brushPreview(this)
142   , m_toolLoopModifiers(tools::ToolLoopModifiers::kNone)
143   , m_padding(0, 0)
144   , m_antsTimer(100, this)
145   , m_antsOffset(0)
146   , m_customizationDelegate(NULL)
147   , m_docView(NULL)
148   , m_flags(flags)
149   , m_secondaryButton(false)
150   , m_aniSpeed(1.0)
151   , m_isPlaying(false)
152   , m_showGuidesThisCel(nullptr)
153   , m_tagFocusBand(-1)
154 {
155   if (!m_renderEngine)
156     m_renderEngine = new EditorRender;
157 
158   m_proj.setPixelRatio(m_sprite->pixelRatio());
159 
160   // Add the first state into the history.
161   m_statesHistory.push(m_state);
162 
163   this->setFocusStop(true);
164 
165   App::instance()->activeToolManager()->add_observer(this);
166 
167   m_fgColorChangeConn =
168     Preferences::instance().colorBar.fgColor.AfterChange.connect(
169       base::Bind<void>(&Editor::onFgColorChange, this));
170 
171   m_contextBarBrushChangeConn =
172     App::instance()->contextBar()->BrushChange.connect(
173       base::Bind<void>(&Editor::onContextBarBrushChange, this));
174 
175   // Restore last site in preferences
176   {
177     frame_t preferredFrame = m_docPref.site.frame();
178     if (preferredFrame >= 0 && preferredFrame <= m_sprite->lastFrame())
179       setFrame(preferredFrame);
180 
181     LayerList layers = m_sprite->allBrowsableLayers();
182     layer_t layerIndex = m_docPref.site.layer();
183     if (layerIndex >= 0 && layerIndex < int(layers.size()))
184       setLayer(layers[layerIndex]);
185   }
186 
187   m_tiledConnBefore = m_docPref.tiled.BeforeChange.connect(base::Bind<void>(&Editor::onTiledModeBeforeChange, this));
188   m_tiledConn = m_docPref.tiled.AfterChange.connect(base::Bind<void>(&Editor::onTiledModeChange, this));
189   m_gridConn = m_docPref.grid.AfterChange.connect(base::Bind<void>(&Editor::invalidate, this));
190   m_pixelGridConn = m_docPref.pixelGrid.AfterChange.connect(base::Bind<void>(&Editor::invalidate, this));
191   m_bgConn = m_docPref.bg.AfterChange.connect(base::Bind<void>(&Editor::invalidate, this));
192   m_onionskinConn = m_docPref.onionskin.AfterChange.connect(base::Bind<void>(&Editor::invalidate, this));
193   m_symmetryModeConn = Preferences::instance().symmetryMode.enabled.AfterChange.connect(base::Bind<void>(&Editor::invalidateIfActive, this));
194   m_showExtrasConn =
195     m_docPref.show.AfterChange.connect(
196       base::Bind<void>(&Editor::onShowExtrasChange, this));
197 
198   m_document->add_observer(this);
199 
200   m_state->onEnterState(this);
201 }
202 
~Editor()203 Editor::~Editor()
204 {
205   if (m_document && m_sprite) {
206     LayerList layers = m_sprite->allBrowsableLayers();
207     layer_t layerIndex = doc::find_layer_index(layers, layer());
208 
209     m_docPref.site.frame(frame());
210     m_docPref.site.layer(layerIndex);
211   }
212 
213   m_observers.notifyDestroyEditor(this);
214   m_document->remove_observer(this);
215   App::instance()->activeToolManager()->remove_observer(this);
216 
217   setCustomizationDelegate(NULL);
218 
219   m_antsTimer.stop();
220 }
221 
destroyEditorSharedInternals()222 void Editor::destroyEditorSharedInternals()
223 {
224   if (m_renderEngine) {
225     delete m_renderEngine;
226     m_renderEngine = nullptr;
227   }
228 }
229 
isActive() const230 bool Editor::isActive() const
231 {
232   return (current_editor == this);
233 }
234 
editor_type()235 WidgetType editor_type()
236 {
237   static WidgetType type = kGenericWidget;
238   if (type == kGenericWidget)
239     type = register_widget_type();
240   return type;
241 }
242 
setStateInternal(const EditorStatePtr & newState)243 void Editor::setStateInternal(const EditorStatePtr& newState)
244 {
245   m_brushPreview.hide();
246 
247   // Fire before change state event, set the state, and fire after
248   // change state event.
249   EditorState::LeaveAction leaveAction =
250     m_state->onLeaveState(this, newState.get());
251 
252   // Push a new state
253   if (newState) {
254     if (leaveAction == EditorState::DiscardState)
255       m_statesHistory.pop();
256 
257     m_statesHistory.push(newState);
258     m_state = newState;
259   }
260   // Go to previous state
261   else {
262     m_state->onBeforePopState(this);
263 
264     // Save the current state into "m_deletedStates" just to keep a
265     // reference to it to avoid delete it right now. We'll delete it
266     // in the next Editor::onProcessMessage().
267     //
268     // This is necessary for PlayState because it removes itself
269     // calling Editor::stop() from PlayState::onPlaybackTick(). If we
270     // delete the PlayState inside the "Tick" timer signal, the
271     // program will crash (because we're iterating the
272     // PlayState::m_playTimer slots).
273     m_deletedStates.push(m_state);
274 
275     m_statesHistory.pop();
276     m_state = m_statesHistory.top();
277   }
278 
279   ASSERT(m_state);
280 
281   // Change to the new state.
282   m_state->onEnterState(this);
283 
284   // Notify observers
285   m_observers.notifyStateChanged(this);
286 
287   // Redraw layer edges
288   if (m_docPref.show.layerEdges())
289     invalidate();
290 
291   // Setup the new mouse cursor
292   setCursor(ui::get_mouse_position());
293 
294   updateStatusBar();
295 }
296 
setState(const EditorStatePtr & newState)297 void Editor::setState(const EditorStatePtr& newState)
298 {
299   setStateInternal(newState);
300 }
301 
backToPreviousState()302 void Editor::backToPreviousState()
303 {
304   setStateInternal(EditorStatePtr(NULL));
305 }
306 
getInvalidDecoratoredRegion(gfx::Region & region)307 void Editor::getInvalidDecoratoredRegion(gfx::Region& region)
308 {
309   // Remove decorated region that cannot be just moved because it
310   // must be redrawn in another position when the Editor's scroll
311   // changes (e.g. symmetry handles).
312   if ((m_flags & kShowDecorators) && m_decorator)
313     m_decorator->getInvalidDecoratoredRegion(this, region);
314 
315 #if ENABLE_DEVMODE
316   // TODO put this in other widget
317   if (Preferences::instance().perf.showRenderTime()) {
318     if (!m_perfInfoBounds.isEmpty())
319       region |= gfx::Region(m_perfInfoBounds);
320   }
321 #endif // ENABLE_DEVMODE
322 }
323 
setLayer(const Layer * layer)324 void Editor::setLayer(const Layer* layer)
325 {
326   bool changed = (m_layer != layer);
327 
328   m_observers.notifyBeforeLayerChanged(this);
329   m_layer = const_cast<Layer*>(layer);
330   m_observers.notifyAfterLayerChanged(this);
331 
332   if (m_document && changed) {
333     if (// If the onion skinning depends on the active layer
334         m_docPref.onionskin.currentLayer() ||
335         // If the user want to see the active layer edges...
336         m_docPref.show.layerEdges() ||
337         // If there is a different opacity for nonactive-layers
338         Preferences::instance().experimental.nonactiveLayersOpacity() < 255 ||
339         // If the automatic cel guides are visible...
340         m_showGuidesThisCel) {
341       // We've to redraw the whole editor
342       invalidate();
343     }
344   }
345 
346   // The active layer has changed.
347   if (isActive())
348     UIContext::instance()->notifyActiveSiteChanged();
349 
350   updateStatusBar();
351 }
352 
setFrame(frame_t frame)353 void Editor::setFrame(frame_t frame)
354 {
355   if (m_frame == frame)
356     return;
357 
358   m_observers.notifyBeforeFrameChanged(this);
359   {
360     HideBrushPreview hide(m_brushPreview);
361     m_frame = frame;
362   }
363   m_observers.notifyAfterFrameChanged(this);
364 
365   // The active frame has changed.
366   if (isActive())
367     UIContext::instance()->notifyActiveSiteChanged();
368 
369   invalidate();
370   updateStatusBar();
371 }
372 
getSite(Site * site) const373 void Editor::getSite(Site* site) const
374 {
375   site->document(m_document);
376   site->sprite(m_sprite);
377   site->layer(m_layer);
378   site->frame(m_frame);
379 }
380 
getSite() const381 Site Editor::getSite() const
382 {
383   Site site;
384   getSite(&site);
385   return site;
386 }
387 
setZoom(const render::Zoom & zoom)388 void Editor::setZoom(const render::Zoom& zoom)
389 {
390   if (m_proj.zoom() != zoom) {
391     m_proj.setZoom(zoom);
392     notifyZoomChanged();
393   }
394   else {
395     // Just copy the zoom as the internal "Zoom::m_internalScale"
396     // value might be different and we want to keep this value updated
397     // for better zooming experience in StateWithWheelBehavior.
398     m_proj.setZoom(zoom);
399   }
400 }
401 
setDefaultScroll()402 void Editor::setDefaultScroll()
403 {
404   View* view = View::getView(this);
405   Rect vp = view->viewportBounds();
406   gfx::Size canvas = canvasSize();
407 
408   setEditorScroll(
409     gfx::Point(
410       m_padding.x - vp.w/2 + m_proj.applyX(canvas.w)/2,
411       m_padding.y - vp.h/2 + m_proj.applyY(canvas.h)/2));
412 }
413 
setScrollAndZoomToFitScreen()414 void Editor::setScrollAndZoomToFitScreen()
415 {
416   View* view = View::getView(this);
417   gfx::Rect vp = view->viewportBounds();
418   gfx::Size canvas = canvasSize();
419   Zoom zoom = m_proj.zoom();
420 
421   if (float(vp.w) / float(canvas.w) <
422       float(vp.h) / float(canvas.h)) {
423     if (vp.w < m_proj.applyX(canvas.w)) {
424       while (vp.w < m_proj.applyX(canvas.w)) {
425         if (!zoom.out())
426           break;
427         m_proj.setZoom(zoom);
428       }
429     }
430     else if (vp.w > m_proj.applyX(canvas.w)) {
431       bool out = true;
432       while (vp.w > m_proj.applyX(canvas.w)) {
433         if (!zoom.in()) {
434           out = false;
435           break;
436         }
437         m_proj.setZoom(zoom);
438       }
439       if (out) {
440         zoom.out();
441         m_proj.setZoom(zoom);
442       }
443     }
444   }
445   else {
446     if (vp.h < m_proj.applyY(canvas.h)) {
447       while (vp.h < m_proj.applyY(canvas.h)) {
448         if (!zoom.out())
449           break;
450         m_proj.setZoom(zoom);
451       }
452     }
453     else if (vp.h > m_proj.applyY(canvas.h)) {
454       bool out = true;
455       while (vp.h > m_proj.applyY(canvas.h)) {
456         if (!zoom.in()) {
457           out = false;
458           break;
459         }
460         m_proj.setZoom(zoom);
461       }
462       if (out) {
463         zoom.out();
464         m_proj.setZoom(zoom);
465       }
466     }
467   }
468 
469   updateEditor();
470   setEditorScroll(
471     gfx::Point(
472       m_padding.x - vp.w/2 + m_proj.applyX(canvas.w)/2,
473       m_padding.y - vp.h/2 + m_proj.applyY(canvas.h)/2));
474 }
475 
476 // Sets the scroll position of the editor
setEditorScroll(const gfx::Point & scroll)477 void Editor::setEditorScroll(const gfx::Point& scroll)
478 {
479   View::getView(this)->setViewScroll(scroll);
480 }
481 
setEditorZoom(const render::Zoom & zoom)482 void Editor::setEditorZoom(const render::Zoom& zoom)
483 {
484   setZoomAndCenterInMouse(
485     zoom, ui::get_mouse_position(),
486     Editor::ZoomBehavior::CENTER);
487 }
488 
updateEditor()489 void Editor::updateEditor()
490 {
491   View::getView(this)->updateView();
492 }
493 
drawOneSpriteUnclippedRect(ui::Graphics * g,const gfx::Rect & spriteRectToDraw,int dx,int dy)494 void Editor::drawOneSpriteUnclippedRect(ui::Graphics* g, const gfx::Rect& spriteRectToDraw, int dx, int dy)
495 {
496   // Clip from sprite and apply zoom
497   gfx::Rect rc = m_sprite->bounds().createIntersection(spriteRectToDraw);
498   rc = m_proj.apply(rc);
499 
500   gfx::Rect dest(dx + m_padding.x + rc.x,
501                  dy + m_padding.y + rc.y, 0, 0);
502 
503   // Clip from graphics/screen
504   const gfx::Rect& clip = g->getClipBounds();
505   if (dest.x < clip.x) {
506     rc.x += clip.x - dest.x;
507     rc.w -= clip.x - dest.x;
508     dest.x = clip.x;
509   }
510   if (dest.y < clip.y) {
511     rc.y += clip.y - dest.y;
512     rc.h -= clip.y - dest.y;
513     dest.y = clip.y;
514   }
515   if (dest.x+rc.w > clip.x+clip.w) {
516     rc.w = clip.x+clip.w-dest.x;
517   }
518   if (dest.y+rc.h > clip.y+clip.h) {
519     rc.h = clip.y+clip.h-dest.y;
520   }
521 
522   if (rc.isEmpty())
523     return;
524 
525   // Bounds of pixels from the sprite canvas that will be exposed in
526   // this render cycle.
527   gfx::Rect expose = m_proj.remove(rc);
528 
529   // If the zoom level is less than 100%, we add extra pixels to
530   // the exposed area. Those pixels could be shown in the
531   // rendering process depending on each cel position.
532   // E.g. when we are drawing in a cel with position < (0,0)
533   if (m_proj.scaleX() < 1.0)
534     expose.enlargeXW(int(1./m_proj.scaleX()));
535   // If the zoom level is more than %100 we add an extra pixel to
536   // expose just in case the zoom requires to display it.  Note:
537   // this is really necessary to avoid showing invalid destination
538   // areas in ToolLoopImpl.
539   else if (m_proj.scaleX() > 1.0)
540     expose.enlargeXW(1);
541 
542   if (m_proj.scaleY() < 1.0)
543     expose.enlargeYH(int(1./m_proj.scaleY()));
544   else if (m_proj.scaleY() > 1.0)
545     expose.enlargeYH(1);
546 
547   expose &= m_sprite->bounds();
548 
549   const int maxw = std::max(0, m_sprite->width()-expose.x);
550   const int maxh = std::max(0, m_sprite->height()-expose.y);
551   expose.w = MID(0, expose.w, maxw);
552   expose.h = MID(0, expose.h, maxh);
553   if (expose.isEmpty())
554     return;
555 
556   // rc2 is the rectangle used to create a temporal rendered image of the sprite
557   const bool newEngine =
558     (Preferences::instance().experimental.newRenderEngine()
559      // Reference layers + zoom > 100% need the old render engine for
560      // sub-pixel rendering.
561      && (!m_sprite->hasVisibleReferenceLayers()
562          || (m_proj.scaleX() <= 1.0
563              && m_proj.scaleY() <= 1.0)));
564   gfx::Rect rc2;
565   if (newEngine) {
566     rc2 = expose;               // New engine, exposed rectangle (without zoom)
567     dest.x = dx + m_padding.x + m_proj.applyX(rc2.x);
568     dest.y = dy + m_padding.y + m_proj.applyY(rc2.y);
569     dest.w = m_proj.applyX(rc2.w);
570     dest.h = m_proj.applyY(rc2.h);
571   }
572   else {
573     rc2 = rc;                   // Old engine, same rectangle with zoom
574     dest.w = rc.w;
575     dest.h = rc.h;
576   }
577 
578   base::UniquePtr<Image> rendered(nullptr);
579   try {
580     // Generate a "expose sprite pixels" notification. This is used by
581     // tool managers that need to validate this region (copy pixels from
582     // the original cel) before it can be used by the RenderEngine.
583     m_document->notifyExposeSpritePixels(m_sprite, gfx::Region(expose));
584 
585     // Create a temporary RGB bitmap to draw all to it
586     rendered.reset(Image::create(IMAGE_RGB, rc2.w, rc2.h,
587                                  m_renderEngine->getRenderImageBuffer()));
588 
589     m_renderEngine->setRefLayersVisiblity(true);
590     m_renderEngine->setSelectedLayer(m_layer);
591     if (m_flags & Editor::kUseNonactiveLayersOpacityWhenEnabled)
592       m_renderEngine->setNonactiveLayersOpacity(Preferences::instance().experimental.nonactiveLayersOpacity());
593     else
594       m_renderEngine->setNonactiveLayersOpacity(255);
595     m_renderEngine->setProjection(
596       newEngine ? render::Projection(): m_proj);
597     m_renderEngine->setupBackground(m_document, rendered->pixelFormat());
598     m_renderEngine->disableOnionskin();
599 
600     if ((m_flags & kShowOnionskin) == kShowOnionskin) {
601       if (m_docPref.onionskin.active()) {
602         OnionskinOptions opts(
603           (m_docPref.onionskin.type() == app::gen::OnionskinType::MERGE ?
604            render::OnionskinType::MERGE:
605            (m_docPref.onionskin.type() == app::gen::OnionskinType::RED_BLUE_TINT ?
606             render::OnionskinType::RED_BLUE_TINT:
607             render::OnionskinType::NONE)));
608 
609         opts.position(m_docPref.onionskin.position());
610         opts.prevFrames(m_docPref.onionskin.prevFrames());
611         opts.nextFrames(m_docPref.onionskin.nextFrames());
612         opts.opacityBase(m_docPref.onionskin.opacityBase());
613         opts.opacityStep(m_docPref.onionskin.opacityStep());
614         opts.layer(m_docPref.onionskin.currentLayer() ? m_layer: nullptr);
615 
616         FrameTag* tag = nullptr;
617         if (m_docPref.onionskin.loopTag())
618           tag = m_sprite->frameTags().innerTag(m_frame);
619         opts.loopTag(tag);
620 
621         m_renderEngine->setOnionskin(opts);
622       }
623     }
624 
625     ExtraCelRef extraCel = m_document->extraCel();
626     if (extraCel && extraCel->type() != render::ExtraType::NONE) {
627       m_renderEngine->setExtraImage(
628         extraCel->type(),
629         extraCel->cel(),
630         extraCel->image(),
631         extraCel->blendMode(),
632         m_layer, m_frame);
633     }
634 
635     m_renderEngine->renderSprite(
636       rendered, m_sprite, m_frame, gfx::Clip(0, 0, rc2));
637 
638     m_renderEngine->removeExtraImage();
639   }
640   catch (const std::exception& e) {
641     Console::showException(e);
642   }
643 
644   if (rendered) {
645     // Convert the render to a she::Surface
646     static she::Surface* tmp = nullptr; // TODO move this to other centralized place
647     if (!tmp || tmp->width() < rc2.w || tmp->height() < rc2.h) {
648       const int maxw = std::max(rc2.w, tmp ? tmp->width(): 0);
649       const int maxh = std::max(rc2.h, tmp ? tmp->height(): 0);
650       if (tmp)
651         tmp->dispose();
652       tmp = she::instance()->createSurface(maxw, maxh);
653     }
654     if (tmp->nativeHandle()) {
655       if (newEngine)
656         tmp->clear(); // TODO why we need this?
657 
658       convert_image_to_surface(rendered, m_sprite->palette(m_frame),
659                                tmp, 0, 0, 0, 0, rc2.w, rc2.h);
660       if (newEngine) {
661         g->drawRgbaSurface(tmp, gfx::Rect(0, 0, rc2.w, rc2.h), dest);
662       }
663       else {
664         g->blit(tmp, 0, 0, dest.x, dest.y, dest.w, dest.h);
665       }
666       m_brushPreview.invalidateRegion(gfx::Region(dest));
667     }
668   }
669 
670   // Draw grids
671   {
672     gfx::Rect enclosingRect(
673       m_padding.x + dx,
674       m_padding.y + dy,
675       m_proj.applyX(m_sprite->width()),
676       m_proj.applyY(m_sprite->height()));
677 
678     IntersectClip clip(g, dest);
679     if (clip) {
680       // Draw the pixel grid
681       if ((m_proj.zoom().scale() > 2.0) && m_docPref.show.pixelGrid()) {
682         int alpha = m_docPref.pixelGrid.opacity();
683 
684         if (m_docPref.pixelGrid.autoOpacity()) {
685           alpha = int(alpha * (m_proj.zoom().scale()-2.) / (16.-2.));
686           alpha = MID(0, alpha, 255);
687         }
688 
689         drawGrid(g, enclosingRect, Rect(0, 0, 1, 1),
690                  m_docPref.pixelGrid.color(), alpha);
691       }
692 
693       // Draw the grid
694       if (m_docPref.show.grid()) {
695         gfx::Rect gridrc = m_docPref.grid.bounds();
696         if (m_proj.applyX(gridrc.w) > 2 &&
697             m_proj.applyY(gridrc.h) > 2) {
698           int alpha = m_docPref.grid.opacity();
699 
700           if (m_docPref.grid.autoOpacity()) {
701             double len = (m_proj.applyX(gridrc.w) +
702                           m_proj.applyY(gridrc.h)) / 2.;
703             alpha = int(alpha * len / 32.);
704             alpha = MID(0, alpha, 255);
705           }
706 
707           if (alpha > 8)
708             drawGrid(g, enclosingRect, m_docPref.grid.bounds(),
709                      m_docPref.grid.color(), alpha);
710         }
711       }
712     }
713   }
714 }
715 
drawBackground(ui::Graphics * g)716 void Editor::drawBackground(ui::Graphics* g)
717 {
718   if (!(m_flags & kShowOutside))
719     return;
720 
721   SkinTheme* theme = static_cast<SkinTheme*>(this->theme());
722 
723   gfx::Size canvas = canvasSize();
724   gfx::Rect rc(0, 0, canvas.w, canvas.h);
725   rc = editorToScreen(rc);
726   rc.offset(-bounds().origin());
727 
728   // Fill the outside (parts of the editor that aren't covered by the
729   // sprite).
730   gfx::Region outside(clientBounds());
731   outside.createSubtraction(outside, gfx::Region(rc));
732   g->fillRegion(theme->colors.editorFace(), outside);
733 
734   // Draw the borders that enclose the sprite.
735   rc.enlarge(1);
736   g->drawRect(theme->colors.editorSpriteBorder(), rc);
737   g->drawHLine(theme->colors.editorSpriteBottomBorder(), rc.x, rc.y2(), rc.w);
738 }
739 
drawSpriteUnclippedRect(ui::Graphics * g,const gfx::Rect & _rc)740 void Editor::drawSpriteUnclippedRect(ui::Graphics* g, const gfx::Rect& _rc)
741 {
742   gfx::Rect rc = _rc;
743   // For odd zoom scales minor than 100% we have to add an extra window
744   // just to make sure the whole rectangle is drawn.
745   if (m_proj.scaleX() < 1.0) rc.w += int(1./m_proj.scaleX());
746   if (m_proj.scaleY() < 1.0) rc.h += int(1./m_proj.scaleY());
747 
748   gfx::Rect client = clientBounds();
749   gfx::Rect spriteRect(
750     client.x + m_padding.x,
751     client.y + m_padding.y,
752     m_proj.applyX(m_sprite->width()),
753     m_proj.applyY(m_sprite->height()));
754   gfx::Rect enclosingRect = spriteRect;
755 
756   // Draw the main sprite at the center.
757   drawOneSpriteUnclippedRect(g, rc, 0, 0);
758 
759   // Document preferences
760   if (int(m_docPref.tiled.mode()) & int(filters::TiledMode::X_AXIS)) {
761     drawOneSpriteUnclippedRect(g, rc, spriteRect.w, 0);
762     drawOneSpriteUnclippedRect(g, rc, spriteRect.w*2, 0);
763 
764     enclosingRect = gfx::Rect(spriteRect.x, spriteRect.y, spriteRect.w*3, spriteRect.h);
765   }
766 
767   if (int(m_docPref.tiled.mode()) & int(filters::TiledMode::Y_AXIS)) {
768     drawOneSpriteUnclippedRect(g, rc, 0, spriteRect.h);
769     drawOneSpriteUnclippedRect(g, rc, 0, spriteRect.h*2);
770 
771     enclosingRect = gfx::Rect(spriteRect.x, spriteRect.y, spriteRect.w, spriteRect.h*3);
772   }
773 
774   if (m_docPref.tiled.mode() == filters::TiledMode::BOTH) {
775     drawOneSpriteUnclippedRect(g, rc, spriteRect.w,   spriteRect.h);
776     drawOneSpriteUnclippedRect(g, rc, spriteRect.w*2, spriteRect.h);
777     drawOneSpriteUnclippedRect(g, rc, spriteRect.w,   spriteRect.h*2);
778     drawOneSpriteUnclippedRect(g, rc, spriteRect.w*2, spriteRect.h*2);
779 
780     enclosingRect = gfx::Rect(
781       spriteRect.x, spriteRect.y,
782       spriteRect.w*3, spriteRect.h*3);
783   }
784 
785   // Draw slices
786   if (m_docPref.show.slices())
787     drawSlices(g);
788 
789   // Symmetry mode
790   if (isActive() &&
791       (m_flags & Editor::kShowSymmetryLine) &&
792       Preferences::instance().symmetryMode.enabled()) {
793     int mode = int(m_docPref.symmetry.mode());
794     if (mode & int(app::gen::SymmetryMode::HORIZONTAL)) {
795       double x = m_docPref.symmetry.xAxis();
796       if (x > 0) {
797         gfx::Color color = color_utils::color_for_ui(m_docPref.grid.color());
798         g->drawVLine(color,
799                      spriteRect.x + m_proj.applyX(mainTilePosition().x) + int(m_proj.applyX<double>(x)),
800                      enclosingRect.y,
801                      enclosingRect.h);
802       }
803     }
804     if (mode & int(app::gen::SymmetryMode::VERTICAL)) {
805       double y = m_docPref.symmetry.yAxis();
806       if (y > 0) {
807         gfx::Color color = color_utils::color_for_ui(m_docPref.grid.color());
808         g->drawHLine(color,
809                      enclosingRect.x,
810                      spriteRect.y + m_proj.applyY(mainTilePosition().y) + int(m_proj.applyY<double>(y)),
811                      enclosingRect.w);
812       }
813     }
814   }
815 
816   // Draw active layer/cel edges
817   bool showGuidesThisCel = this->showAutoCelGuides();
818   if ((m_docPref.show.layerEdges() || showGuidesThisCel) &&
819       // Show layer edges only on "standby" like states where brush
820       // preview is shown (e.g. with this we avoid to showing the
821       // edges in states like DrawingState, etc.).
822       m_state->requireBrushPreview()) {
823     Cel* cel = (m_layer ? m_layer->cel(m_frame): nullptr);
824     if (cel) {
825       drawCelBounds(
826         g, cel,
827         color_utils::color_for_ui(Preferences::instance().guides.layerEdgesColor()));
828 
829       if (showGuidesThisCel &&
830           m_showGuidesThisCel != cel)
831         drawCelGuides(g, cel, m_showGuidesThisCel);
832     }
833   }
834 
835   // Draw the mask
836   if (m_document->getMaskBoundaries())
837     drawMask(g);
838 
839   // Post-render decorator.
840   if ((m_flags & kShowDecorators) && m_decorator) {
841     EditorPostRenderImpl postRender(this, g);
842     m_decorator->postRenderDecorator(&postRender);
843   }
844 }
845 
drawSpriteClipped(const gfx::Region & updateRegion)846 void Editor::drawSpriteClipped(const gfx::Region& updateRegion)
847 {
848   Region screenRegion;
849   getDrawableRegion(screenRegion, kCutTopWindows);
850 
851   ScreenGraphics screenGraphics;
852   GraphicsPtr editorGraphics = getGraphics(clientBounds());
853 
854   for (const Rect& updateRect : updateRegion) {
855     for (const Rect& screenRect : screenRegion) {
856       IntersectClip clip(&screenGraphics, screenRect);
857       if (clip)
858         drawSpriteUnclippedRect(editorGraphics.get(), updateRect);
859     }
860   }
861 }
862 
863 /**
864  * Draws the boundaries, really this routine doesn't use the "mask"
865  * field of the sprite, only the "bound" field (so you can have other
866  * mask in the sprite and could be showed other boundaries), to
867  * regenerate boundaries, use the sprite_generate_mask_boundaries()
868  * routine.
869  */
drawMask(Graphics * g)870 void Editor::drawMask(Graphics* g)
871 {
872   if ((m_flags & kShowMask) == 0 ||
873       !m_docPref.show.selectionEdges())
874     return;
875 
876   ASSERT(m_document->getMaskBoundaries());
877 
878   gfx::Point pt = mainTilePosition();
879   pt.x = m_padding.x + m_proj.applyX(pt.x);
880   pt.y = m_padding.y + m_proj.applyY(pt.y);
881 
882   for (const auto& seg : *m_document->getMaskBoundaries()) {
883     CheckedDrawMode checked(g, m_antsOffset,
884                             gfx::rgba(0, 0, 0, 255),
885                             gfx::rgba(255, 255, 255, 255));
886     gfx::Rect bounds = m_proj.apply(seg.bounds());
887 
888     if (m_proj.scaleX() >= 1.0) {
889       if (!seg.open() && seg.vertical())
890         --bounds.x;
891     }
892 
893     if (m_proj.scaleY() >= 1.0) {
894       if (!seg.open() && !seg.vertical())
895         --bounds.y;
896     }
897 
898     // The color doesn't matter, we are using CheckedDrawMode
899     if (seg.vertical())
900       g->drawVLine(gfx::rgba(0, 0, 0), pt.x+bounds.x, pt.y+bounds.y, bounds.h);
901     else
902       g->drawHLine(gfx::rgba(0, 0, 0), pt.x+bounds.x, pt.y+bounds.y, bounds.w);
903   }
904 }
905 
drawMaskSafe()906 void Editor::drawMaskSafe()
907 {
908   if ((m_flags & kShowMask) == 0)
909     return;
910 
911   if (isVisible() &&
912       m_document &&
913       m_document->getMaskBoundaries()) {
914     Region region;
915     getDrawableRegion(region, kCutTopWindows);
916     region.offset(-bounds().origin());
917 
918     HideBrushPreview hide(m_brushPreview);
919     GraphicsPtr g = getGraphics(clientBounds());
920 
921     for (const gfx::Rect& rc : region) {
922       IntersectClip clip(g.get(), rc);
923       if (clip)
924         drawMask(g.get());
925     }
926   }
927 }
928 
drawGrid(Graphics * g,const gfx::Rect & spriteBounds,const Rect & gridBounds,const app::Color & color,int alpha)929 void Editor::drawGrid(Graphics* g, const gfx::Rect& spriteBounds, const Rect& gridBounds, const app::Color& color, int alpha)
930 {
931   if ((m_flags & kShowGrid) == 0)
932     return;
933 
934   // Copy the grid bounds
935   Rect grid(gridBounds);
936   if (grid.w < 1 || grid.h < 1)
937     return;
938 
939   // Move the grid bounds to a non-negative position.
940   if (grid.x < 0) grid.x += (ABS(grid.x)/grid.w+1) * grid.w;
941   if (grid.y < 0) grid.y += (ABS(grid.y)/grid.h+1) * grid.h;
942 
943   // Change the grid position to the first grid's tile
944   grid.setOrigin(Point((grid.x % grid.w) - grid.w,
945                        (grid.y % grid.h) - grid.h));
946   if (grid.x < 0) grid.x += grid.w;
947   if (grid.y < 0) grid.y += grid.h;
948 
949   // Convert the "grid" rectangle to screen coordinates
950   grid = editorToScreen(grid);
951   if (grid.w < 1 || grid.h < 1)
952     return;
953 
954   // Adjust for client area
955   gfx::Rect bounds = this->bounds();
956   grid.offset(-bounds.origin());
957 
958   while (grid.x-grid.w >= spriteBounds.x) grid.x -= grid.w;
959   while (grid.y-grid.h >= spriteBounds.y) grid.y -= grid.h;
960 
961   // Get the grid's color
962   gfx::Color grid_color = color_utils::color_for_ui(color);
963   grid_color = gfx::rgba(
964     gfx::getr(grid_color),
965     gfx::getg(grid_color),
966     gfx::getb(grid_color), alpha);
967 
968   // Draw horizontal lines
969   int x1 = spriteBounds.x;
970   int y1 = grid.y;
971   int x2 = spriteBounds.x + spriteBounds.w;
972   int y2 = spriteBounds.y + spriteBounds.h;
973 
974   for (int c=y1; c<=y2; c+=grid.h)
975     g->drawHLine(grid_color, x1, c, spriteBounds.w);
976 
977   // Draw vertical lines
978   x1 = grid.x;
979   y1 = spriteBounds.y;
980 
981   for (int c=x1; c<=x2; c+=grid.w)
982     g->drawVLine(grid_color, c, y1, spriteBounds.h);
983 }
984 
drawSlices(ui::Graphics * g)985 void Editor::drawSlices(ui::Graphics* g)
986 {
987   if ((m_flags & kShowSlices) == 0)
988     return;
989 
990   if (!isVisible() || !m_document)
991     return;
992 
993   gfx::Point mainOffset(mainTilePosition());
994 
995   for (auto slice : m_sprite->slices()) {
996     auto key = slice->getByFrame(m_frame);
997     if (!key)
998       continue;
999 
1000     doc::color_t docColor = slice->userData().color();
1001     gfx::Color color = gfx::rgba(doc::rgba_getr(docColor),
1002                                  doc::rgba_getg(docColor),
1003                                  doc::rgba_getb(docColor),
1004                                  doc::rgba_geta(docColor));
1005     gfx::Rect out = key->bounds();
1006     out.offset(mainOffset);
1007     out = editorToScreen(out);
1008     out.offset(-bounds().origin());
1009 
1010     // Center slices
1011     if (key->hasCenter()) {
1012       gfx::Rect in =
1013         editorToScreen(gfx::Rect(key->center()).offset(key->bounds().origin()))
1014         .offset(-bounds().origin());
1015 
1016       auto in_color = gfx::rgba(gfx::getr(color),
1017                                 gfx::getg(color),
1018                                 gfx::getb(color),
1019                                 doc::rgba_geta(docColor)/4);
1020       if (in.y > out.y && in.y < out.y2())
1021         g->drawHLine(in_color, out.x, in.y, out.w);
1022       if (in.y2() > out.y && in.y2() < out.y2())
1023         g->drawHLine(in_color, out.x, in.y2(), out.w);
1024       if (in.x > out.x && in.x < out.x2())
1025         g->drawVLine(in_color, in.x, out.y, out.h);
1026       if (in.x2() > out.x && in.x2() < out.x2())
1027         g->drawVLine(in_color, in.x2(), out.y, out.h);
1028     }
1029 
1030     // Pivot
1031     if (key->hasPivot()) {
1032       gfx::Rect in =
1033         editorToScreen(gfx::Rect(key->pivot(), gfx::Size(1, 1)).offset(key->bounds().origin()))
1034         .offset(-bounds().origin());
1035 
1036       auto in_color = gfx::rgba(gfx::getr(color),
1037                                 gfx::getg(color),
1038                                 gfx::getb(color),
1039                                 doc::rgba_geta(docColor)/4);
1040       g->drawRect(in_color, in);
1041     }
1042 
1043     g->drawRect(color, out);
1044   }
1045 }
1046 
drawCelBounds(ui::Graphics * g,const Cel * cel,const gfx::Color color)1047 void Editor::drawCelBounds(ui::Graphics* g, const Cel* cel, const gfx::Color color)
1048 {
1049   g->drawRect(color, getCelScreenBounds(cel));
1050 }
1051 
drawCelGuides(ui::Graphics * g,const Cel * cel,const Cel * mouseCel)1052 void Editor::drawCelGuides(ui::Graphics* g, const Cel* cel, const Cel* mouseCel)
1053 {
1054   gfx::Rect
1055     sprCelBounds = cel->bounds(),
1056     scrCelBounds = getCelScreenBounds(cel),
1057     scrCmpBounds, sprCmpBounds;
1058   if (mouseCel) {
1059     scrCmpBounds = getCelScreenBounds(mouseCel);
1060     sprCmpBounds = mouseCel->bounds();
1061 
1062     drawCelBounds(
1063       g, mouseCel,
1064       color_utils::color_for_ui(Preferences::instance().guides.autoGuidesColor()));
1065   }
1066   // Use whole canvas
1067   else {
1068     sprCmpBounds = m_sprite->bounds();
1069     scrCmpBounds =
1070       editorToScreen(
1071         gfx::Rect(sprCmpBounds).offset(mainTilePosition()))
1072       .offset(gfx::Point(-bounds().origin()));
1073   }
1074 
1075   const int midX = scrCelBounds.x+scrCelBounds.w/2;
1076   const int midY = scrCelBounds.y+scrCelBounds.h/2;
1077 
1078   if (sprCelBounds.x2() < sprCmpBounds.x) {
1079     drawCelHGuide(g,
1080                   sprCelBounds.x2(), sprCmpBounds.x,
1081                   scrCelBounds.x2(), scrCmpBounds.x, midY,
1082                   scrCelBounds, scrCmpBounds, scrCmpBounds.x);
1083   }
1084   else if (sprCelBounds.x > sprCmpBounds.x2()) {
1085     drawCelHGuide(g,
1086                   sprCmpBounds.x2(), sprCelBounds.x,
1087                   scrCmpBounds.x2(), scrCelBounds.x, midY,
1088                   scrCelBounds, scrCmpBounds, scrCmpBounds.x2()-1);
1089   }
1090   else {
1091     if (sprCelBounds.x != sprCmpBounds.x &&
1092         sprCelBounds.x2() != sprCmpBounds.x) {
1093       drawCelHGuide(g,
1094                     sprCmpBounds.x, sprCelBounds.x,
1095                     scrCmpBounds.x, scrCelBounds.x, midY,
1096                     scrCelBounds, scrCmpBounds, scrCmpBounds.x);
1097     }
1098     if (sprCelBounds.x != sprCmpBounds.x2() &&
1099         sprCelBounds.x2() != sprCmpBounds.x2()) {
1100       drawCelHGuide(g,
1101                     sprCmpBounds.x2(), sprCelBounds.x2(),
1102                     scrCmpBounds.x2(), scrCelBounds.x2(), midY,
1103                     scrCelBounds, scrCmpBounds, scrCmpBounds.x2()-1);
1104     }
1105   }
1106 
1107   if (sprCelBounds.y2() < sprCmpBounds.y) {
1108     drawCelVGuide(g,
1109                   sprCelBounds.y2(), sprCmpBounds.y,
1110                   scrCelBounds.y2(), scrCmpBounds.y, midX,
1111                   scrCelBounds, scrCmpBounds, scrCmpBounds.y);
1112   }
1113   else if (sprCelBounds.y > sprCmpBounds.y2()) {
1114     drawCelVGuide(g,
1115                   sprCmpBounds.y2(), sprCelBounds.y,
1116                   scrCmpBounds.y2(), scrCelBounds.y, midX,
1117                   scrCelBounds, scrCmpBounds, scrCmpBounds.y2()-1);
1118   }
1119   else {
1120     if (sprCelBounds.y != sprCmpBounds.y &&
1121         sprCelBounds.y2() != sprCmpBounds.y) {
1122       drawCelVGuide(g,
1123                     sprCmpBounds.y, sprCelBounds.y,
1124                     scrCmpBounds.y, scrCelBounds.y, midX,
1125                     scrCelBounds, scrCmpBounds, scrCmpBounds.y);
1126     }
1127     if (sprCelBounds.y != sprCmpBounds.y2() &&
1128         sprCelBounds.y2() != sprCmpBounds.y2()) {
1129       drawCelVGuide(g,
1130                     sprCmpBounds.y2(), sprCelBounds.y2(),
1131                     scrCmpBounds.y2(), scrCelBounds.y2(), midX,
1132                     scrCelBounds, scrCmpBounds, scrCmpBounds.y2()-1);
1133     }
1134   }
1135 }
1136 
drawCelHGuide(ui::Graphics * g,const int sprX1,const int sprX2,const int scrX1,const int scrX2,const int scrY,const gfx::Rect & scrCelBounds,const gfx::Rect & scrCmpBounds,const int dottedX)1137 void Editor::drawCelHGuide(ui::Graphics* g,
1138                            const int sprX1, const int sprX2,
1139                            const int scrX1, const int scrX2, const int scrY,
1140                            const gfx::Rect& scrCelBounds, const gfx::Rect& scrCmpBounds,
1141                            const int dottedX)
1142 {
1143   gfx::Color color = color_utils::color_for_ui(Preferences::instance().guides.autoGuidesColor());
1144   g->drawHLine(color, scrX1, scrY, scrX2 - scrX1);
1145 
1146   // Vertical guide to touch the horizontal line
1147   {
1148     CheckedDrawMode checked(g, 0, color, gfx::ColorNone);
1149 
1150     if (scrY < scrCmpBounds.y)
1151       g->drawVLine(color, dottedX, scrCelBounds.y, scrCmpBounds.y - scrCelBounds.y);
1152     else if (scrY > scrCmpBounds.y2())
1153       g->drawVLine(color, dottedX, scrCmpBounds.y2(), scrCelBounds.y2() - scrCmpBounds.y2());
1154   }
1155 
1156   auto text = base::convert_to<std::string>(ABS(sprX2 - sprX1)) + "px";
1157   const int textW = Graphics::measureUITextLength(text, font());
1158   g->drawText(text,
1159               color_utils::blackandwhite_neg(color), color,
1160               gfx::Point((scrX1+scrX2)/2-textW/2, scrY-textHeight()));
1161 }
1162 
drawCelVGuide(ui::Graphics * g,const int sprY1,const int sprY2,const int scrY1,const int scrY2,const int scrX,const gfx::Rect & scrCelBounds,const gfx::Rect & scrCmpBounds,const int dottedY)1163 void Editor::drawCelVGuide(ui::Graphics* g,
1164                            const int sprY1, const int sprY2,
1165                            const int scrY1, const int scrY2, const int scrX,
1166                            const gfx::Rect& scrCelBounds, const gfx::Rect& scrCmpBounds,
1167                            const int dottedY)
1168 {
1169   gfx::Color color = color_utils::color_for_ui(Preferences::instance().guides.autoGuidesColor());
1170   g->drawVLine(color, scrX, scrY1, scrY2 - scrY1);
1171 
1172   // Horizontal guide to touch the vertical line
1173   {
1174     CheckedDrawMode checked(g, 0, color, gfx::ColorNone);
1175 
1176     if (scrX < scrCmpBounds.x)
1177       g->drawHLine(color, scrCelBounds.x, dottedY, scrCmpBounds.x - scrCelBounds.x);
1178     else if (scrX > scrCmpBounds.x2())
1179       g->drawHLine(color, scrCmpBounds.x2(), dottedY, scrCelBounds.x2() - scrCmpBounds.x2());
1180   }
1181 
1182   auto text = base::convert_to<std::string>(ABS(sprY2 - sprY1)) + "px";
1183   g->drawText(text,
1184               color_utils::blackandwhite_neg(color), color,
1185               gfx::Point(scrX, (scrY1+scrY2)/2-textHeight()/2));
1186 }
1187 
getCelScreenBounds(const Cel * cel)1188 gfx::Rect Editor::getCelScreenBounds(const Cel* cel)
1189 {
1190   gfx::Point mainOffset(mainTilePosition());
1191   gfx::Rect layerEdges;
1192   if (m_layer->isReference()) {
1193     layerEdges =
1194       editorToScreenF(
1195         gfx::RectF(cel->boundsF()).offset(mainOffset.x,
1196                                           mainOffset.y))
1197       .offset(gfx::PointF(-bounds().origin()));
1198   }
1199   else {
1200     layerEdges =
1201       editorToScreen(
1202         gfx::Rect(cel->bounds()).offset(mainOffset))
1203       .offset(-bounds().origin());
1204   }
1205   return layerEdges;
1206 }
1207 
flashCurrentLayer()1208 void Editor::flashCurrentLayer()
1209 {
1210   if (!Preferences::instance().experimental.flashLayer())
1211     return;
1212 
1213   Site site = getSite();
1214 
1215   int x, y;
1216   const Image* src_image = site.image(&x, &y);
1217   if (src_image) {
1218     m_renderEngine->removePreviewImage();
1219 
1220     ExtraCelRef extraCel(new ExtraCel);
1221     extraCel->create(m_sprite, m_sprite->bounds(), m_frame, 255);
1222     extraCel->setType(render::ExtraType::COMPOSITE);
1223     extraCel->setBlendMode(BlendMode::NEG_BW);
1224 
1225     Image* flash_image = extraCel->image();
1226     clear_image(flash_image, flash_image->maskColor());
1227     copy_image(flash_image, src_image, x, y);
1228 
1229     {
1230       ExtraCelRef oldExtraCel = m_document->extraCel();
1231       m_document->setExtraCel(extraCel);
1232       drawSpriteClipped(gfx::Region(
1233                           gfx::Rect(0, 0, m_sprite->width(), m_sprite->height())));
1234       manager()->flipDisplay();
1235       m_document->setExtraCel(oldExtraCel);
1236     }
1237 
1238     invalidate();
1239   }
1240 }
1241 
autoScroll(MouseMessage * msg,AutoScroll dir)1242 gfx::Point Editor::autoScroll(MouseMessage* msg, AutoScroll dir)
1243 {
1244   gfx::Point mousePos = msg->position();
1245   if (!Preferences::instance().editor.autoScroll())
1246     return mousePos;
1247 
1248   // Hide the brush preview
1249   //HideBrushPreview hide(m_brushPreview);
1250   View* view = View::getView(this);
1251   gfx::Rect vp = view->viewportBounds();
1252 
1253   if (!vp.contains(mousePos)) {
1254     gfx::Point delta = (mousePos - m_oldPos);
1255     gfx::Point deltaScroll = delta;
1256 
1257     if (!((mousePos.x <  vp.x      && delta.x < 0) ||
1258           (mousePos.x >= vp.x+vp.w && delta.x > 0))) {
1259       delta.x = 0;
1260     }
1261 
1262     if (!((mousePos.y <  vp.y      && delta.y < 0) ||
1263           (mousePos.y >= vp.y+vp.h && delta.y > 0))) {
1264       delta.y = 0;
1265     }
1266 
1267     gfx::Point scroll = view->viewScroll();
1268     if (dir == AutoScroll::MouseDir) {
1269       scroll += delta;
1270     }
1271     else {
1272       scroll -= deltaScroll;
1273     }
1274     setEditorScroll(scroll);
1275 
1276 #if defined(_WIN32) || defined(__APPLE__)
1277     mousePos -= delta;
1278     ui::set_mouse_position(mousePos);
1279 #endif
1280 
1281     m_oldPos = mousePos;
1282     mousePos = gfx::Point(
1283       MID(vp.x, mousePos.x, vp.x+vp.w-1),
1284       MID(vp.y, mousePos.y, vp.y+vp.h-1));
1285   }
1286   else
1287     m_oldPos = mousePos;
1288 
1289   return mousePos;
1290 }
1291 
getCurrentEditorTool()1292 tools::Tool* Editor::getCurrentEditorTool()
1293 {
1294   return App::instance()->activeTool();
1295 }
1296 
getCurrentEditorInk()1297 tools::Ink* Editor::getCurrentEditorInk()
1298 {
1299   tools::Ink* ink = m_state->getStateInk();
1300   if (ink)
1301     return ink;
1302   else
1303     return App::instance()->activeToolManager()->activeInk();
1304 }
1305 
isAutoSelectLayer() const1306 bool Editor::isAutoSelectLayer() const
1307 {
1308   return App::instance()->contextBar()->isAutoSelectLayer();
1309 }
1310 
screenToEditor(const gfx::Point & pt)1311 gfx::Point Editor::screenToEditor(const gfx::Point& pt)
1312 {
1313   View* view = View::getView(this);
1314   Rect vp = view->viewportBounds();
1315   Point scroll = view->viewScroll();
1316   return gfx::Point(
1317     m_proj.removeX(pt.x - vp.x + scroll.x - m_padding.x),
1318     m_proj.removeY(pt.y - vp.y + scroll.y - m_padding.y));
1319 }
1320 
screenToEditorF(const gfx::Point & pt)1321 gfx::PointF Editor::screenToEditorF(const gfx::Point& pt)
1322 {
1323   View* view = View::getView(this);
1324   Rect vp = view->viewportBounds();
1325   Point scroll = view->viewScroll();
1326   return gfx::PointF(
1327     m_proj.removeX<double>(pt.x - vp.x + scroll.x - m_padding.x),
1328     m_proj.removeY<double>(pt.y - vp.y + scroll.y - m_padding.y));
1329 }
1330 
editorToScreen(const gfx::Point & pt)1331 Point Editor::editorToScreen(const gfx::Point& pt)
1332 {
1333   View* view = View::getView(this);
1334   Rect vp = view->viewportBounds();
1335   Point scroll = view->viewScroll();
1336   return Point(
1337     (vp.x - scroll.x + m_padding.x + m_proj.applyX(pt.x)),
1338     (vp.y - scroll.y + m_padding.y + m_proj.applyY(pt.y)));
1339 }
1340 
editorToScreenF(const gfx::PointF & pt)1341 gfx::PointF Editor::editorToScreenF(const gfx::PointF& pt)
1342 {
1343   View* view = View::getView(this);
1344   Rect vp = view->viewportBounds();
1345   Point scroll = view->viewScroll();
1346   return PointF(
1347     (vp.x - scroll.x + m_padding.x + m_proj.applyX<double>(pt.x)),
1348     (vp.y - scroll.y + m_padding.y + m_proj.applyY<double>(pt.y)));
1349 }
1350 
screenToEditor(const Rect & rc)1351 Rect Editor::screenToEditor(const Rect& rc)
1352 {
1353   return gfx::Rect(
1354     screenToEditor(rc.origin()),
1355     screenToEditor(rc.point2()));
1356 }
1357 
editorToScreen(const Rect & rc)1358 Rect Editor::editorToScreen(const Rect& rc)
1359 {
1360   return gfx::Rect(
1361     editorToScreen(rc.origin()),
1362     editorToScreen(rc.point2()));
1363 }
1364 
editorToScreenF(const gfx::RectF & rc)1365 gfx::RectF Editor::editorToScreenF(const gfx::RectF& rc)
1366 {
1367   return gfx::RectF(
1368     editorToScreenF(rc.origin()),
1369     editorToScreenF(rc.point2()));
1370 }
1371 
add_observer(EditorObserver * observer)1372 void Editor::add_observer(EditorObserver* observer)
1373 {
1374   m_observers.add_observer(observer);
1375 }
1376 
remove_observer(EditorObserver * observer)1377 void Editor::remove_observer(EditorObserver* observer)
1378 {
1379   m_observers.remove_observer(observer);
1380 }
1381 
setCustomizationDelegate(EditorCustomizationDelegate * delegate)1382 void Editor::setCustomizationDelegate(EditorCustomizationDelegate* delegate)
1383 {
1384   if (m_customizationDelegate)
1385     m_customizationDelegate->dispose();
1386 
1387   m_customizationDelegate = delegate;
1388 }
1389 
getViewportBounds()1390 Rect Editor::getViewportBounds()
1391 {
1392   return screenToEditor(View::getView(this)->viewportBounds());
1393 }
1394 
1395 // Returns the visible area of the active sprite.
getVisibleSpriteBounds()1396 Rect Editor::getVisibleSpriteBounds()
1397 {
1398   if (m_sprite)
1399     return getViewportBounds().createIntersection(m_sprite->bounds());
1400 
1401   // This cannot happen, the sprite must be != nullptr. In old
1402   // Aseprite versions we were using one Editor to show multiple
1403   // sprites (switching the sprite inside the editor). Now we have one
1404   // (or more) editor(s) for each sprite.
1405   ASSERT(false);
1406 
1407   // Return an empty rectangle if there is not a active sprite.
1408   return Rect();
1409 }
1410 
1411 // Changes the scroll to see the given point as the center of the editor.
centerInSpritePoint(const gfx::Point & spritePos)1412 void Editor::centerInSpritePoint(const gfx::Point& spritePos)
1413 {
1414   HideBrushPreview hide(m_brushPreview);
1415   View* view = View::getView(this);
1416   Rect vp = view->viewportBounds();
1417 
1418   gfx::Point scroll(
1419     m_padding.x - (vp.w/2) + m_proj.applyX(1)/2 + m_proj.applyX(spritePos.x),
1420     m_padding.y - (vp.h/2) + m_proj.applyY(1)/2 + m_proj.applyY(spritePos.y));
1421 
1422   updateEditor();
1423   setEditorScroll(scroll);
1424   invalidate();
1425 }
1426 
updateStatusBar()1427 void Editor::updateStatusBar()
1428 {
1429   if (!hasMouse())
1430     return;
1431 
1432   // Setup status bar using the current editor's state
1433   m_state->onUpdateStatusBar(this);
1434 }
1435 
updateQuicktool()1436 void Editor::updateQuicktool()
1437 {
1438   if (m_customizationDelegate && !hasCapture()) {
1439     auto activeToolManager = App::instance()->activeToolManager();
1440     tools::Tool* selectedTool = activeToolManager->selectedTool();
1441 
1442     // Don't change quicktools if we are in a selection tool and using
1443     // the selection modifiers.
1444     if (selectedTool->getInk(0)->isSelection() &&
1445         int(m_customizationDelegate->getPressedKeyAction(KeyContext::SelectionTool)) != 0)
1446       return;
1447 
1448     tools::Tool* newQuicktool =
1449       m_customizationDelegate->getQuickTool(selectedTool);
1450 
1451     // Check if the current state accept the given quicktool.
1452     if (newQuicktool && !m_state->acceptQuickTool(newQuicktool))
1453       return;
1454 
1455     activeToolManager
1456       ->newQuickToolSelectedFromEditor(newQuicktool);
1457   }
1458 }
1459 
updateToolByTipProximity(ui::PointerType pointerType)1460 void Editor::updateToolByTipProximity(ui::PointerType pointerType)
1461 {
1462   auto activeToolManager = App::instance()->activeToolManager();
1463 
1464   if (pointerType == ui::PointerType::Eraser) {
1465     activeToolManager->eraserTipProximity();
1466   }
1467   else {
1468     activeToolManager->regularTipProximity();
1469   }
1470 }
1471 
updateToolLoopModifiersIndicators()1472 void Editor::updateToolLoopModifiersIndicators()
1473 {
1474   int modifiers = int(tools::ToolLoopModifiers::kNone);
1475   const bool autoSelectLayer = isAutoSelectLayer();
1476   bool newAutoSelectLayer = autoSelectLayer;
1477   KeyAction action;
1478 
1479   if (m_customizationDelegate) {
1480     // When the mouse is captured, is when we are scrolling, or
1481     // drawing, or moving, or selecting, etc. So several
1482     // parameters/tool-loop-modifiers are static.
1483     if (hasCapture()) {
1484       modifiers |= (int(m_toolLoopModifiers) &
1485                     (int(tools::ToolLoopModifiers::kReplaceSelection) |
1486                      int(tools::ToolLoopModifiers::kAddSelection) |
1487                      int(tools::ToolLoopModifiers::kSubtractSelection)));
1488 
1489       tools::Controller* controller =
1490         (App::instance()->activeToolManager()->selectedTool() ?
1491          App::instance()->activeToolManager()->selectedTool()->getController(0): nullptr);
1492 
1493       // Shape tools modifiers (line, curves, rectangles, etc.)
1494       if (controller && controller->isTwoPoints()) {
1495         action = m_customizationDelegate->getPressedKeyAction(KeyContext::ShapeTool);
1496         if (int(action & KeyAction::MoveOrigin))
1497           modifiers |= int(tools::ToolLoopModifiers::kMoveOrigin);
1498         if (int(action & KeyAction::SquareAspect))
1499           modifiers |= int(tools::ToolLoopModifiers::kSquareAspect);
1500         if (int(action & KeyAction::DrawFromCenter))
1501           modifiers |= int(tools::ToolLoopModifiers::kFromCenter);
1502         if (int(action & KeyAction::RotateShape))
1503           modifiers |= int(tools::ToolLoopModifiers::kRotateShape);
1504       }
1505 
1506       // Freehand modifiers
1507       if (controller && controller->isFreehand()) {
1508         action = m_customizationDelegate->getPressedKeyAction(KeyContext::FreehandTool);
1509         if (int(action & KeyAction::AngleSnapFromLastPoint))
1510           modifiers |= int(tools::ToolLoopModifiers::kSquareAspect);
1511       }
1512     }
1513     else {
1514       // We update the selection mode only if we're not selecting.
1515       action = m_customizationDelegate->getPressedKeyAction(KeyContext::SelectionTool);
1516 
1517       gen::SelectionMode mode = Preferences::instance().selection.mode();
1518       if (int(action & KeyAction::SubtractSelection) ||
1519           // Don't use "subtract" mode if the selection was activated
1520           // with the "right click mode = a selection-like tool"
1521           (m_secondaryButton &&
1522            App::instance()->activeToolManager()->selectedTool() &&
1523            App::instance()->activeToolManager()->selectedTool()->getInk(0)->isSelection())) {
1524         mode = gen::SelectionMode::SUBTRACT;
1525       }
1526       else if (int(action & KeyAction::AddSelection)) {
1527         mode = gen::SelectionMode::ADD;
1528       }
1529       switch (mode) {
1530         case gen::SelectionMode::DEFAULT:  modifiers |= int(tools::ToolLoopModifiers::kReplaceSelection);  break;
1531         case gen::SelectionMode::ADD:      modifiers |= int(tools::ToolLoopModifiers::kAddSelection);      break;
1532         case gen::SelectionMode::SUBTRACT: modifiers |= int(tools::ToolLoopModifiers::kSubtractSelection); break;
1533       }
1534 
1535       // For move tool
1536       action = m_customizationDelegate->getPressedKeyAction(KeyContext::MoveTool);
1537       if (int(action & KeyAction::AutoSelectLayer))
1538         newAutoSelectLayer = true;
1539       else
1540         newAutoSelectLayer = Preferences::instance().editor.autoSelectLayer();
1541     }
1542   }
1543 
1544   ContextBar* ctxBar = App::instance()->contextBar();
1545 
1546   if (int(m_toolLoopModifiers) != modifiers) {
1547     m_toolLoopModifiers = tools::ToolLoopModifiers(modifiers);
1548 
1549     // TODO the contextbar should be a observer of the current editor
1550     ctxBar->updateToolLoopModifiersIndicators(m_toolLoopModifiers);
1551 
1552     if (auto drawingState = dynamic_cast<DrawingState*>(m_state.get())) {
1553       drawingState->notifyToolLoopModifiersChange(this);
1554     }
1555   }
1556 
1557   if (autoSelectLayer != newAutoSelectLayer)
1558     ctxBar->updateAutoSelectLayer(newAutoSelectLayer);
1559 }
1560 
getColorByPosition(const gfx::Point & mousePos)1561 app::Color Editor::getColorByPosition(const gfx::Point& mousePos)
1562 {
1563   Site site = getSite();
1564   if (site.sprite()) {
1565     gfx::PointF editorPos = screenToEditorF(mousePos);
1566 
1567     ColorPicker picker;
1568     picker.pickColor(site, editorPos, m_proj,
1569                      ColorPicker::FromComposition);
1570     return picker.color();
1571   }
1572   else
1573     return app::Color::fromMask();
1574 }
1575 
startStraightLineWithFreehandTool(const ui::MouseMessage * msg)1576 bool Editor::startStraightLineWithFreehandTool(const ui::MouseMessage* msg)
1577 {
1578   tools::Tool* tool = App::instance()->activeToolManager()->selectedTool();
1579   // TODO add support for more buttons (X1, X2, etc.)
1580   int i = (msg && msg->right() ? 1: 0);
1581   return
1582     (isActive() &&
1583      (hasMouse() || hasCapture()) &&
1584      tool &&
1585      tool->getController(i)->isFreehand() &&
1586      tool->getInk(i)->isPaint() &&
1587      (getCustomizationDelegate()
1588       ->getPressedKeyAction(KeyContext::FreehandTool) & KeyAction::StraightLineFromLastPoint) == KeyAction::StraightLineFromLastPoint &&
1589      document()->lastDrawingPoint() != Doc::NoLastDrawingPoint());
1590 }
1591 
1592 //////////////////////////////////////////////////////////////////////
1593 // Message handler for the editor
1594 
onProcessMessage(Message * msg)1595 bool Editor::onProcessMessage(Message* msg)
1596 {
1597   // Delete states
1598   if (!m_deletedStates.empty())
1599     m_deletedStates.clear();
1600 
1601   switch (msg->type()) {
1602 
1603     case kTimerMessage:
1604       if (static_cast<TimerMessage*>(msg)->timer() == &m_antsTimer) {
1605         if (isVisible() && m_sprite) {
1606           drawMaskSafe();
1607 
1608           // Set offset to make selection-movement effect
1609           if (m_antsOffset < 7)
1610             m_antsOffset++;
1611           else
1612             m_antsOffset = 0;
1613         }
1614         else if (m_antsTimer.isRunning()) {
1615           m_antsTimer.stop();
1616         }
1617       }
1618       break;
1619 
1620     case kMouseEnterMessage:
1621       m_brushPreview.hide();
1622       updateToolLoopModifiersIndicators();
1623       updateQuicktool();
1624       break;
1625 
1626     case kMouseLeaveMessage:
1627       m_brushPreview.hide();
1628       StatusBar::instance()->clearText();
1629       break;
1630 
1631     case kMouseDownMessage:
1632       if (m_sprite) {
1633         MouseMessage* mouseMsg = static_cast<MouseMessage*>(msg);
1634 
1635         m_oldPos = mouseMsg->position();
1636         updateToolByTipProximity(mouseMsg->pointerType());
1637         updateAutoCelGuides(msg);
1638 
1639         // Only when we right-click with the regular "paint bg-color
1640         // right-click mode" we will mark indicate that the secondary
1641         // button was used (m_secondaryButton == true).
1642         if (mouseMsg->right() && !m_secondaryButton) {
1643           m_secondaryButton = true;
1644         }
1645 
1646         updateToolLoopModifiersIndicators();
1647         updateQuicktool();
1648         setCursor(mouseMsg->position());
1649 
1650         App::instance()->activeToolManager()
1651           ->pressButton(pointer_from_msg(this, mouseMsg));
1652 
1653         EditorStatePtr holdState(m_state);
1654         return m_state->onMouseDown(this, mouseMsg);
1655       }
1656       break;
1657 
1658     case kMouseMoveMessage:
1659       if (m_sprite) {
1660         EditorStatePtr holdState(m_state);
1661         MouseMessage* mouseMsg = static_cast<MouseMessage*>(msg);
1662 
1663         updateToolByTipProximity(mouseMsg->pointerType());
1664         updateAutoCelGuides(msg);
1665 
1666         return m_state->onMouseMove(this, static_cast<MouseMessage*>(msg));
1667       }
1668       break;
1669 
1670     case kMouseUpMessage:
1671       if (m_sprite) {
1672         EditorStatePtr holdState(m_state);
1673         MouseMessage* mouseMsg = static_cast<MouseMessage*>(msg);
1674         bool result = m_state->onMouseUp(this, mouseMsg);
1675 
1676         updateToolByTipProximity(mouseMsg->pointerType());
1677         updateAutoCelGuides(msg);
1678 
1679         if (!hasCapture()) {
1680           App::instance()->activeToolManager()->releaseButtons();
1681           m_secondaryButton = false;
1682 
1683           updateToolLoopModifiersIndicators();
1684           updateQuicktool();
1685           setCursor(mouseMsg->position());
1686         }
1687 
1688         if (result)
1689           return true;
1690       }
1691       break;
1692 
1693     case kDoubleClickMessage:
1694       if (m_sprite) {
1695         MouseMessage* mouseMsg = static_cast<MouseMessage*>(msg);
1696         EditorStatePtr holdState(m_state);
1697 
1698         updateToolByTipProximity(mouseMsg->pointerType());
1699 
1700         bool used = m_state->onDoubleClick(this, mouseMsg);
1701         if (used)
1702           return true;
1703       }
1704       break;
1705 
1706     case kTouchMagnifyMessage:
1707       if (m_sprite) {
1708         EditorStatePtr holdState(m_state);
1709         return m_state->onTouchMagnify(this, static_cast<TouchMessage*>(msg));
1710       }
1711       break;
1712 
1713     case kKeyDownMessage:
1714 #if ENABLE_DEVMODE
1715       // Switch render mode
1716       if (!msg->ctrlPressed() &&
1717           static_cast<KeyMessage*>(msg)->scancode() == kKeyF1) {
1718         Preferences::instance().experimental.newRenderEngine(
1719           !Preferences::instance().experimental.newRenderEngine());
1720         invalidate();
1721         return true;
1722       }
1723 #endif
1724       if (m_sprite) {
1725         EditorStatePtr holdState(m_state);
1726         bool used = m_state->onKeyDown(this, static_cast<KeyMessage*>(msg));
1727 
1728         updateToolLoopModifiersIndicators();
1729         updateAutoCelGuides(msg);
1730         if (hasMouse()) {
1731           updateQuicktool();
1732           setCursor(ui::get_mouse_position());
1733         }
1734 
1735         if (used)
1736           return true;
1737       }
1738       break;
1739 
1740     case kKeyUpMessage:
1741       if (m_sprite) {
1742         EditorStatePtr holdState(m_state);
1743         bool used = m_state->onKeyUp(this, static_cast<KeyMessage*>(msg));
1744 
1745         updateToolLoopModifiersIndicators();
1746         updateAutoCelGuides(msg);
1747         if (hasMouse()) {
1748           updateQuicktool();
1749           setCursor(ui::get_mouse_position());
1750         }
1751 
1752         if (used)
1753           return true;
1754       }
1755       break;
1756 
1757     case kFocusLeaveMessage:
1758       // As we use keys like Space-bar as modifier, we can clear the
1759       // keyboard buffer when we lost the focus.
1760       she::instance()->clearKeyboardBuffer();
1761       break;
1762 
1763     case kMouseWheelMessage:
1764       if (m_sprite && hasMouse()) {
1765         EditorStatePtr holdState(m_state);
1766         if (m_state->onMouseWheel(this, static_cast<MouseMessage*>(msg)))
1767           return true;
1768       }
1769       break;
1770 
1771     case kSetCursorMessage:
1772       setCursor(static_cast<MouseMessage*>(msg)->position());
1773       return true;
1774   }
1775 
1776   return Widget::onProcessMessage(msg);
1777 }
1778 
onSizeHint(SizeHintEvent & ev)1779 void Editor::onSizeHint(SizeHintEvent& ev)
1780 {
1781   gfx::Size sz(0, 0);
1782   if (m_sprite) {
1783     gfx::Point padding = calcExtraPadding(m_proj);
1784     gfx::Size canvas = canvasSize();
1785     sz.w = m_proj.applyX(canvas.w) + padding.x*2;
1786     sz.h = m_proj.applyY(canvas.h) + padding.y*2;
1787   }
1788   else {
1789     sz.w = 4;
1790     sz.h = 4;
1791   }
1792   ev.setSizeHint(sz);
1793 }
1794 
onResize(ui::ResizeEvent & ev)1795 void Editor::onResize(ui::ResizeEvent& ev)
1796 {
1797   Widget::onResize(ev);
1798   m_padding = calcExtraPadding(m_proj);
1799 }
1800 
onPaint(ui::PaintEvent & ev)1801 void Editor::onPaint(ui::PaintEvent& ev)
1802 {
1803   std::unique_ptr<HideBrushPreview> hide;
1804   // If we are drawing the editor for a tooltip background or any
1805   // other semi-transparent widget (e.g. popups), we destroy the brush
1806   // preview/extra cel to avoid drawing a part of the brush in the
1807   // transparent widget background.
1808   if (ev.isTransparentBg()) {
1809     m_brushPreview.discardBrushPreview();
1810   }
1811   else {
1812     hide.reset(new HideBrushPreview(m_brushPreview));
1813   }
1814 
1815   Graphics* g = ev.graphics();
1816   gfx::Rect rc = clientBounds();
1817   SkinTheme* theme = static_cast<SkinTheme*>(this->theme());
1818 
1819   // Editor without sprite
1820   if (!m_sprite) {
1821     g->fillRect(theme->colors.editorFace(), rc);
1822   }
1823   // Editor with sprite
1824   else {
1825     try {
1826       // Lock the sprite to read/render it. We wait 1/4 secs in case
1827       // the background thread is making a backup.
1828       DocReader documentReader(m_document, 250);
1829 
1830       // Draw the sprite in the editor
1831       renderChrono.reset();
1832       drawBackground(g);
1833       drawSpriteUnclippedRect(g, gfx::Rect(0, 0, m_sprite->width(), m_sprite->height()));
1834       renderElapsed = renderChrono.elapsed();
1835 
1836 #if ENABLE_DEVMODE
1837       // Show performance stats (TODO show performance stats in other widget)
1838       if (Preferences::instance().perf.showRenderTime()) {
1839         View* view = View::getView(this);
1840         gfx::Rect vp = view->viewportBounds();
1841         char buf[128];
1842         sprintf(buf, "%c %.4gs",
1843                 Preferences::instance().experimental.newRenderEngine() ? 'N': 'O',
1844                 renderElapsed);
1845         g->drawText(
1846           buf,
1847           gfx::rgba(255, 255, 255, 255),
1848           gfx::rgba(0, 0, 0, 255),
1849           vp.origin() - bounds().origin());
1850 
1851         m_perfInfoBounds.setOrigin(vp.origin());
1852         m_perfInfoBounds.setSize(g->measureUIText(buf));
1853       }
1854 #endif // ENABLE_DEVMODE
1855 
1856       // Draw the mask boundaries
1857       if (m_document->getMaskBoundaries()) {
1858         drawMask(g);
1859         m_antsTimer.start();
1860       }
1861       else {
1862         m_antsTimer.stop();
1863       }
1864     }
1865     catch (const LockedDocException&) {
1866       // The sprite is locked to be read, so we can draw an opaque
1867       // background only.
1868       g->fillRect(theme->colors.editorFace(), rc);
1869       defer_invalid_rect(g->getClipBounds().offset(bounds().origin()));
1870     }
1871   }
1872 }
1873 
onInvalidateRegion(const gfx::Region & region)1874 void Editor::onInvalidateRegion(const gfx::Region& region)
1875 {
1876   Widget::onInvalidateRegion(region);
1877   m_brushPreview.invalidateRegion(region);
1878 }
1879 
1880 // When the current tool is changed
onActiveToolChange(tools::Tool * tool)1881 void Editor::onActiveToolChange(tools::Tool* tool)
1882 {
1883   m_state->onActiveToolChange(this, tool);
1884   if (hasMouse()) {
1885     updateStatusBar();
1886     setCursor(ui::get_mouse_position());
1887   }
1888 }
1889 
onFgColorChange()1890 void Editor::onFgColorChange()
1891 {
1892   m_brushPreview.redraw();
1893 }
1894 
onContextBarBrushChange()1895 void Editor::onContextBarBrushChange()
1896 {
1897   m_brushPreview.redraw();
1898 }
1899 
onTiledModeBeforeChange()1900 void Editor::onTiledModeBeforeChange()
1901 {
1902   m_oldMainTilePos = mainTilePosition();
1903 }
1904 
onTiledModeChange()1905 void Editor::onTiledModeChange()
1906 {
1907   ASSERT(m_sprite);
1908 
1909   // Get the sprite point in the middle of the editor, so we can
1910   // restore this with the new tiled mode in the main tile.
1911   View* view = View::getView(this);
1912   gfx::Rect vp = view->viewportBounds();
1913   gfx::Point screenPos(vp.x + vp.w/2,
1914                        vp.y + vp.h/2);
1915   gfx::Point spritePos(screenToEditor(screenPos));
1916   spritePos -= m_oldMainTilePos;
1917 
1918   // Update padding
1919   m_padding = calcExtraPadding(m_proj);
1920 
1921   spritePos += mainTilePosition();
1922   screenPos = editorToScreen(spritePos);
1923 
1924   centerInSpritePoint(spritePos);
1925 }
1926 
onShowExtrasChange()1927 void Editor::onShowExtrasChange()
1928 {
1929   invalidate();
1930 }
1931 
onExposeSpritePixels(DocEvent & ev)1932 void Editor::onExposeSpritePixels(DocEvent& ev)
1933 {
1934   if (m_state && ev.sprite() == m_sprite)
1935     m_state->onExposeSpritePixels(ev.region());
1936 }
1937 
onSpritePixelRatioChanged(DocEvent & ev)1938 void Editor::onSpritePixelRatioChanged(DocEvent& ev)
1939 {
1940   m_proj.setPixelRatio(ev.sprite()->pixelRatio());
1941   invalidate();
1942 }
1943 
onBeforeRemoveLayer(DocEvent & ev)1944 void Editor::onBeforeRemoveLayer(DocEvent& ev)
1945 {
1946   m_showGuidesThisCel = nullptr;
1947 }
1948 
onRemoveCel(DocEvent & ev)1949 void Editor::onRemoveCel(DocEvent& ev)
1950 {
1951   m_showGuidesThisCel = nullptr;
1952 }
1953 
onAddFrameTag(DocEvent & ev)1954 void Editor::onAddFrameTag(DocEvent& ev)
1955 {
1956   m_tagFocusBand = -1;
1957 }
1958 
onRemoveFrameTag(DocEvent & ev)1959 void Editor::onRemoveFrameTag(DocEvent& ev)
1960 {
1961   m_tagFocusBand = -1;
1962 }
1963 
setCursor(const gfx::Point & mouseScreenPos)1964 void Editor::setCursor(const gfx::Point& mouseScreenPos)
1965 {
1966   Rect vp = View::getView(this)->viewportBounds();
1967   if (!vp.contains(mouseScreenPos))
1968     return;
1969 
1970   bool used = false;
1971   if (m_sprite)
1972     used = m_state->onSetCursor(this, mouseScreenPos);
1973 
1974   if (!used)
1975     showMouseCursor(kArrowCursor);
1976 }
1977 
canDraw()1978 bool Editor::canDraw()
1979 {
1980   return (m_layer != NULL &&
1981           m_layer->isImage() &&
1982           m_layer->isVisibleHierarchy() &&
1983           m_layer->isEditableHierarchy() &&
1984           !m_layer->isReference());
1985 }
1986 
isInsideSelection()1987 bool Editor::isInsideSelection()
1988 {
1989   gfx::Point spritePos = screenToEditor(ui::get_mouse_position());
1990   spritePos -= mainTilePosition();
1991 
1992   KeyAction action = m_customizationDelegate->getPressedKeyAction(KeyContext::SelectionTool);
1993   return
1994     (action == KeyAction::None) &&
1995     m_document &&
1996     m_document->isMaskVisible() &&
1997     m_document->mask()->containsPoint(spritePos.x, spritePos.y);
1998 }
1999 
canStartMovingSelectionPixels()2000 bool Editor::canStartMovingSelectionPixels()
2001 {
2002   return
2003     isInsideSelection() &&
2004     // We cannot move the selection when add/subtract modes are
2005     // enabled (we prefer to modify the selection on those modes
2006     // instead of moving pixels).
2007     ((int(m_toolLoopModifiers) & int(tools::ToolLoopModifiers::kReplaceSelection)) ||
2008      // We can move the selection on add mode if the preferences says so.
2009      ((int(m_toolLoopModifiers) & int(tools::ToolLoopModifiers::kAddSelection)) &&
2010       Preferences::instance().selection.moveOnAddMode()) ||
2011      // We can move the selection when the Copy selection key (Ctrl) is pressed.
2012      (m_customizationDelegate &&
2013       int(m_customizationDelegate->getPressedKeyAction(KeyContext::TranslatingSelection) & KeyAction::CopySelection)));
2014 }
2015 
calcHit(const gfx::Point & mouseScreenPos)2016 EditorHit Editor::calcHit(const gfx::Point& mouseScreenPos)
2017 {
2018   tools::Ink* ink = getCurrentEditorInk();
2019 
2020   if (ink) {
2021     // Check if we can transform slices
2022     if (ink->isSlice()) {
2023       if (m_docPref.show.slices()) {
2024         gfx::Point mainOffset(mainTilePosition());
2025 
2026         for (auto slice : m_sprite->slices()) {
2027           auto key = slice->getByFrame(m_frame);
2028           if (key) {
2029             gfx::Rect bounds = key->bounds();
2030             bounds.offset(mainOffset);
2031             bounds = editorToScreen(bounds);
2032 
2033             gfx::Rect center = key->center();
2034 
2035             // Move bounds
2036             if (bounds.contains(mouseScreenPos) &&
2037                 !bounds.shrink(5*guiscale()).contains(mouseScreenPos)) {
2038               int border =
2039                 (mouseScreenPos.x <= bounds.x ? LEFT: 0) |
2040                 (mouseScreenPos.y <= bounds.y ? TOP: 0) |
2041                 (mouseScreenPos.x >= bounds.x2() ? RIGHT: 0) |
2042                 (mouseScreenPos.y >= bounds.y2() ? BOTTOM: 0);
2043 
2044               EditorHit hit(EditorHit::SliceBounds);
2045               hit.setBorder(border);
2046               hit.setSlice(slice);
2047               return hit;
2048             }
2049 
2050             // Move center
2051             if (!center.isEmpty()) {
2052               center = editorToScreen(
2053                 center.offset(key->bounds().origin()));
2054 
2055               bool horz1 = gfx::Rect(bounds.x, center.y-2*guiscale(), bounds.w, 5*guiscale()).contains(mouseScreenPos);
2056               bool horz2 = gfx::Rect(bounds.x, center.y2()-2*guiscale(), bounds.w, 5*guiscale()).contains(mouseScreenPos);
2057               bool vert1 = gfx::Rect(center.x-2*guiscale(), bounds.y, 5*guiscale(), bounds.h).contains(mouseScreenPos);
2058               bool vert2 = gfx::Rect(center.x2()-2*guiscale(), bounds.y, 5*guiscale(), bounds.h).contains(mouseScreenPos);
2059 
2060               if (horz1 || horz2 || vert1 || vert2) {
2061                 int border =
2062                   (horz1 ? TOP: 0) |
2063                   (horz2 ? BOTTOM: 0) |
2064                   (vert1 ? LEFT: 0) |
2065                   (vert2 ? RIGHT: 0);
2066                 EditorHit hit(EditorHit::SliceCenter);
2067                 hit.setBorder(border);
2068                 hit.setSlice(slice);
2069               return hit;
2070               }
2071             }
2072 
2073             // Move all the slice
2074             if (bounds.contains(mouseScreenPos)) {
2075               EditorHit hit(EditorHit::SliceBounds);
2076               hit.setBorder(CENTER | MIDDLE);
2077               hit.setSlice(slice);
2078               return hit;
2079             }
2080           }
2081         }
2082       }
2083     }
2084   }
2085 
2086   return EditorHit(EditorHit::None);
2087 }
2088 
setZoomAndCenterInMouse(const Zoom & zoom,const gfx::Point & mousePos,ZoomBehavior zoomBehavior)2089 void Editor::setZoomAndCenterInMouse(const Zoom& zoom,
2090                                      const gfx::Point& mousePos,
2091                                      ZoomBehavior zoomBehavior)
2092 {
2093   HideBrushPreview hide(m_brushPreview);
2094   View* view = View::getView(this);
2095   Rect vp = view->viewportBounds();
2096   Projection proj = m_proj;
2097   proj.setZoom(zoom);
2098 
2099   gfx::Point screenPos;
2100   gfx::Point spritePos;
2101   gfx::PointT<double> subpixelPos(0.5, 0.5);
2102 
2103   switch (zoomBehavior) {
2104     case ZoomBehavior::CENTER:
2105       screenPos = gfx::Point(vp.x + vp.w/2,
2106                              vp.y + vp.h/2);
2107       break;
2108     case ZoomBehavior::MOUSE:
2109       screenPos = mousePos;
2110       break;
2111   }
2112   spritePos = screenToEditor(screenPos);
2113 
2114   if (zoomBehavior == ZoomBehavior::MOUSE) {
2115     gfx::Point screenPos2 = editorToScreen(spritePos);
2116 
2117     if (m_proj.scaleX() > 1.0) {
2118       subpixelPos.x = (0.5 + screenPos.x - screenPos2.x) / m_proj.scaleX();
2119       if (proj.scaleX() > m_proj.scaleX()) {
2120         double t = 1.0 / proj.scaleX();
2121         if (subpixelPos.x >= 0.5-t && subpixelPos.x <= 0.5+t)
2122           subpixelPos.x = 0.5;
2123       }
2124     }
2125 
2126     if (m_proj.scaleY() > 1.0) {
2127       subpixelPos.y = (0.5 + screenPos.y - screenPos2.y) / m_proj.scaleY();
2128       if (proj.scaleY() > m_proj.scaleY()) {
2129         double t = 1.0 / proj.scaleY();
2130         if (subpixelPos.y >= 0.5-t && subpixelPos.y <= 0.5+t)
2131           subpixelPos.y = 0.5;
2132       }
2133     }
2134   }
2135 
2136   gfx::Point padding = calcExtraPadding(proj);
2137   gfx::Point scrollPos(
2138     padding.x - (screenPos.x-vp.x) + proj.applyX(spritePos.x+proj.removeX(1)/2) + int(proj.applyX(subpixelPos.x)),
2139     padding.y - (screenPos.y-vp.y) + proj.applyY(spritePos.y+proj.removeY(1)/2) + int(proj.applyY(subpixelPos.y)));
2140 
2141   setZoom(zoom);
2142 
2143   if ((m_proj.zoom() != zoom) || (screenPos != view->viewScroll())) {
2144     updateEditor();
2145     setEditorScroll(scrollPos);
2146   }
2147 
2148   flushRedraw();
2149 }
2150 
pasteImage(const Image * image,const Mask * mask)2151 void Editor::pasteImage(const Image* image, const Mask* mask)
2152 {
2153   ASSERT(image);
2154 
2155   base::UniquePtr<Mask> temp_mask;
2156   if (!mask) {
2157     gfx::Rect visibleBounds = getVisibleSpriteBounds();
2158     gfx::Rect imageBounds = image->bounds();
2159 
2160     temp_mask.reset(new Mask);
2161     temp_mask->replace(
2162       gfx::Rect(visibleBounds.x + visibleBounds.w/2 - imageBounds.w/2,
2163                 visibleBounds.y + visibleBounds.h/2 - imageBounds.h/2,
2164                 imageBounds.w, imageBounds.h));
2165 
2166     mask = temp_mask.get();
2167   }
2168 
2169   // Change to a selection tool: it's necessary for PixelsMovement
2170   // which will use the extra cel for transformation preview, and is
2171   // not compatible with the drawing cursor preview which overwrite
2172   // the extra cel.
2173   if (!getCurrentEditorInk()->isSelection()) {
2174     tools::Tool* defaultSelectionTool =
2175       App::instance()->toolBox()->getToolById(tools::WellKnownTools::RectangularMarquee);
2176 
2177     ToolBar::instance()->selectTool(defaultSelectionTool);
2178   }
2179 
2180   Sprite* sprite = this->sprite();
2181 
2182   // Check bounds where the image will be pasted.
2183   int x = mask->bounds().x;
2184   int y = mask->bounds().y;
2185   {
2186     const Rect visibleBounds = getViewportBounds();
2187     const Point maskCenter = mask->bounds().center();
2188 
2189     // If the pasted image original location center point isn't
2190     // visible, we center the image in the editor's visible bounds.
2191     if (maskCenter.x < visibleBounds.x ||
2192         maskCenter.x >= visibleBounds.x2()) {
2193       x = visibleBounds.x + visibleBounds.w/2 - image->width()/2;
2194     }
2195     // In other case, if the center is visible, we put the pasted
2196     // image in its original location.
2197     else {
2198       x = MID(visibleBounds.x-image->width(), x, visibleBounds.x+visibleBounds.w-1);
2199     }
2200 
2201     if (maskCenter.y < visibleBounds.y ||
2202         maskCenter.y >= visibleBounds.y2()) {
2203       y = visibleBounds.y + visibleBounds.h/2 - image->height()/2;
2204     }
2205     else {
2206       y = MID(visibleBounds.y-image->height(), y, visibleBounds.y+visibleBounds.h-1);
2207     }
2208 
2209     // Limit the image inside the sprite's bounds.
2210     if (sprite->width() <= image->width() ||
2211         sprite->height() <= image->height()) {
2212       x = MID(0, x, sprite->width() - image->width());
2213       y = MID(0, y, sprite->height() - image->height());
2214     }
2215     else {
2216       // Also we always limit the 1 image pixel inside the sprite's bounds.
2217       x = MID(-image->width()+1, x, sprite->width()-1);
2218       y = MID(-image->height()+1, y, sprite->height()-1);
2219     }
2220   }
2221 
2222   // Clear brush preview, as the extra cel will be replaced with the
2223   // pasted image.
2224   m_brushPreview.hide();
2225 
2226   Mask mask2(*mask);
2227   mask2.setOrigin(x, y);
2228 
2229   PixelsMovementPtr pixelsMovement(
2230     new PixelsMovement(UIContext::instance(), getSite(),
2231                        image, &mask2, "Paste"));
2232 
2233   setState(EditorStatePtr(new MovingPixelsState(this, NULL, pixelsMovement, NoHandle)));
2234 }
2235 
startSelectionTransformation(const gfx::Point & move,double angle)2236 void Editor::startSelectionTransformation(const gfx::Point& move, double angle)
2237 {
2238   if (MovingPixelsState* movingPixels = dynamic_cast<MovingPixelsState*>(m_state.get())) {
2239     movingPixels->translate(move);
2240     if (std::fabs(angle) > 1e-5)
2241       movingPixels->rotate(angle);
2242   }
2243   else if (StandbyState* standby = dynamic_cast<StandbyState*>(m_state.get())) {
2244     standby->startSelectionTransformation(this, move, angle);
2245   }
2246 }
2247 
startFlipTransformation(doc::algorithm::FlipType flipType)2248 void Editor::startFlipTransformation(doc::algorithm::FlipType flipType)
2249 {
2250   if (MovingPixelsState* movingPixels = dynamic_cast<MovingPixelsState*>(m_state.get()))
2251     movingPixels->flip(flipType);
2252   else if (StandbyState* standby = dynamic_cast<StandbyState*>(m_state.get()))
2253     standby->startFlipTransformation(this, flipType);
2254 }
2255 
notifyScrollChanged()2256 void Editor::notifyScrollChanged()
2257 {
2258   m_observers.notifyScrollChanged(this);
2259 }
2260 
notifyZoomChanged()2261 void Editor::notifyZoomChanged()
2262 {
2263   m_observers.notifyZoomChanged(this);
2264 }
2265 
checkForScroll(ui::MouseMessage * msg)2266 bool Editor::checkForScroll(ui::MouseMessage* msg)
2267 {
2268   tools::Ink* clickedInk = getCurrentEditorInk();
2269 
2270   // Start scroll loop
2271   if (msg->middle() || clickedInk->isScrollMovement()) { // TODO msg->middle() should be customizable
2272     startScrollingState(msg);
2273     return true;
2274   }
2275   else
2276     return false;
2277 }
2278 
checkForZoom(ui::MouseMessage * msg)2279 bool Editor::checkForZoom(ui::MouseMessage* msg)
2280 {
2281   tools::Ink* clickedInk = getCurrentEditorInk();
2282 
2283   // Start scroll loop
2284   if (clickedInk->isZoom()) {
2285     startZoomingState(msg);
2286     return true;
2287   }
2288   else
2289     return false;
2290 }
2291 
startScrollingState(ui::MouseMessage * msg)2292 void Editor::startScrollingState(ui::MouseMessage* msg)
2293 {
2294   EditorStatePtr newState(new ScrollingState);
2295   setState(newState);
2296   newState->onMouseDown(this, msg);
2297 }
2298 
startZoomingState(ui::MouseMessage * msg)2299 void Editor::startZoomingState(ui::MouseMessage* msg)
2300 {
2301   EditorStatePtr newState(new ZoomingState);
2302   setState(newState);
2303   newState->onMouseDown(this, msg);
2304 }
2305 
play(const bool playOnce,const bool playAll)2306 void Editor::play(const bool playOnce,
2307                   const bool playAll)
2308 {
2309   ASSERT(m_state);
2310   if (!m_state)
2311     return;
2312 
2313   if (m_isPlaying)
2314     stop();
2315 
2316   m_isPlaying = true;
2317   setState(EditorStatePtr(new PlayState(playOnce, playAll)));
2318 }
2319 
stop()2320 void Editor::stop()
2321 {
2322   ASSERT(m_state);
2323   if (!m_state)
2324     return;
2325 
2326   if (m_isPlaying) {
2327     while (m_state && !dynamic_cast<PlayState*>(m_state.get()))
2328       backToPreviousState();
2329 
2330     m_isPlaying = false;
2331 
2332     ASSERT(m_state && dynamic_cast<PlayState*>(m_state.get()));
2333     if (m_state)
2334       backToPreviousState();
2335   }
2336 }
2337 
isPlaying() const2338 bool Editor::isPlaying() const
2339 {
2340   return m_isPlaying;
2341 }
2342 
showAnimationSpeedMultiplierPopup(Option<bool> & playOnce,Option<bool> & playAll,const bool withStopBehaviorOptions)2343 void Editor::showAnimationSpeedMultiplierPopup(Option<bool>& playOnce,
2344                                                Option<bool>& playAll,
2345                                                const bool withStopBehaviorOptions)
2346 {
2347   const double options[] = { 0.25, 0.5, 1.0, 1.5, 2.0, 3.0 };
2348   Menu menu;
2349 
2350   for (double option : options) {
2351     MenuItem* item = new MenuItem("Speed x" + base::convert_to<std::string>(option));
2352     item->Click.connect(base::Bind<void>(&Editor::setAnimationSpeedMultiplier, this, option));
2353     item->setSelected(m_aniSpeed == option);
2354     menu.addChild(item);
2355   }
2356 
2357   menu.addChild(new MenuSeparator);
2358 
2359   // Play once option
2360   {
2361     MenuItem* item = new MenuItem("Play Once");
2362     item->Click.connect(
2363       [&playOnce]() {
2364         playOnce(!playOnce());
2365       });
2366     item->setSelected(playOnce());
2367     menu.addChild(item);
2368   }
2369 
2370   // Play all option
2371   {
2372     MenuItem* item = new MenuItem("Play All Frames (Ignore Tags)");
2373     item->Click.connect(
2374       [&playAll]() {
2375         playAll(!playAll());
2376       });
2377     item->setSelected(playAll());
2378     menu.addChild(item);
2379   }
2380 
2381   if (withStopBehaviorOptions) {
2382     MenuItem* item = new MenuItem("Rewind on Stop");
2383     item->Click.connect(
2384       []() {
2385         // Switch the "rewind_on_stop" option
2386         Preferences::instance().general.rewindOnStop(
2387           !Preferences::instance().general.rewindOnStop());
2388       });
2389     item->setSelected(Preferences::instance().general.rewindOnStop());
2390     menu.addChild(item);
2391   }
2392 
2393   menu.showPopup(ui::get_mouse_position());
2394 
2395   if (isPlaying()) {
2396     // Re-play
2397     stop();
2398     play(playOnce(),
2399          playAll());
2400   }
2401 }
2402 
getAnimationSpeedMultiplier() const2403 double Editor::getAnimationSpeedMultiplier() const
2404 {
2405   return m_aniSpeed;
2406 }
2407 
setAnimationSpeedMultiplier(double speed)2408 void Editor::setAnimationSpeedMultiplier(double speed)
2409 {
2410   m_aniSpeed = speed;
2411 }
2412 
showMouseCursor(CursorType cursorType,const Cursor * cursor)2413 void Editor::showMouseCursor(CursorType cursorType,
2414                              const Cursor* cursor)
2415 {
2416   m_brushPreview.hide();
2417   ui::set_mouse_cursor(cursorType, cursor);
2418 }
2419 
showBrushPreview(const gfx::Point & screenPos)2420 void Editor::showBrushPreview(const gfx::Point& screenPos)
2421 {
2422   if (Preferences::instance().cursor.paintingCursorType() !=
2423       app::gen::PaintingCursorType::SIMPLE_CROSSHAIR)
2424     ui::set_mouse_cursor(kNoCursor);
2425 
2426   m_brushPreview.show(screenPos);
2427 }
2428 
calcExtraPadding(const Projection & proj)2429 gfx::Point Editor::calcExtraPadding(const Projection& proj)
2430 {
2431   View* view = View::getView(this);
2432   if (view) {
2433     Rect vp = view->viewportBounds();
2434     gfx::Size canvas = canvasSize();
2435     return gfx::Point(
2436       std::max<int>(vp.w/2, vp.w - proj.applyX(canvas.w)),
2437       std::max<int>(vp.h/2, vp.h - proj.applyY(canvas.h)));
2438   }
2439   else
2440     return gfx::Point(0, 0);
2441 }
2442 
canvasSize() const2443 gfx::Size Editor::canvasSize() const
2444 {
2445   gfx::Size sz(m_sprite->width(),
2446                m_sprite->height());
2447   if (int(m_docPref.tiled.mode()) & int(filters::TiledMode::X_AXIS)) {
2448     sz.w += sz.w*2;
2449   }
2450   if (int(m_docPref.tiled.mode()) & int(filters::TiledMode::Y_AXIS)) {
2451     sz.h += sz.h*2;
2452   }
2453   return sz;
2454 }
2455 
mainTilePosition() const2456 gfx::Point Editor::mainTilePosition() const
2457 {
2458   gfx::Point pt(0, 0);
2459   if (int(m_docPref.tiled.mode()) & int(filters::TiledMode::X_AXIS)) {
2460     pt.x += m_sprite->width();
2461   }
2462   if (int(m_docPref.tiled.mode()) & int(filters::TiledMode::Y_AXIS)) {
2463     pt.y += m_sprite->height();
2464   }
2465   return pt;
2466 }
2467 
expandRegionByTiledMode(gfx::Region & rgn,const bool withProj) const2468 void Editor::expandRegionByTiledMode(gfx::Region& rgn,
2469                                      const bool withProj) const
2470 {
2471   gfx::Region tile = rgn;
2472   const bool xTiled = (int(m_docPref.tiled.mode()) & int(filters::TiledMode::X_AXIS));
2473   const bool yTiled = (int(m_docPref.tiled.mode()) & int(filters::TiledMode::Y_AXIS));
2474   int w = m_sprite->width();
2475   int h = m_sprite->height();
2476   if (withProj) {
2477     w = m_proj.applyX(w);
2478     h = m_proj.applyY(h);
2479   }
2480   if (xTiled) {
2481     tile.offset(w, 0); rgn |= tile;
2482     tile.offset(w, 0); rgn |= tile;
2483     tile.offset(-2*w, 0);
2484   }
2485   if (yTiled) {
2486     tile.offset(0, h); rgn |= tile;
2487     tile.offset(0, h); rgn |= tile;
2488     tile.offset(0, -2*h);
2489   }
2490   if (xTiled && yTiled) {
2491     tile.offset(w, h); rgn |= tile;
2492     tile.offset(w, 0); rgn |= tile;
2493     tile.offset(-w, h); rgn |= tile;
2494     tile.offset(w, 0); rgn |= tile;
2495   }
2496 }
2497 
isMovingPixels() const2498 bool Editor::isMovingPixels() const
2499 {
2500   return (dynamic_cast<MovingPixelsState*>(m_state.get()) != nullptr);
2501 }
2502 
dropMovingPixels()2503 void Editor::dropMovingPixels()
2504 {
2505   ASSERT(isMovingPixels());
2506   backToPreviousState();
2507 }
2508 
invalidateIfActive()2509 void Editor::invalidateIfActive()
2510 {
2511   if (isActive())
2512     invalidate();
2513 }
2514 
showAutoCelGuides()2515 bool Editor::showAutoCelGuides()
2516 {
2517   return
2518     (getCurrentEditorInk()->isCelMovement() &&
2519      m_docPref.show.autoGuides() &&
2520      m_customizationDelegate &&
2521      int(m_customizationDelegate->getPressedKeyAction(KeyContext::MoveTool) & KeyAction::AutoSelectLayer));
2522 }
2523 
updateAutoCelGuides(ui::Message * msg)2524 void Editor::updateAutoCelGuides(ui::Message* msg)
2525 {
2526   Cel* oldShowGuidesThisCel = m_showGuidesThisCel;
2527 
2528   // Check if the user is pressing the Ctrl or Cmd key on move
2529   // tool to show automatic guides.
2530   if (showAutoCelGuides() &&
2531       m_state->requireBrushPreview()) {
2532     ui::MouseMessage* mouseMsg = dynamic_cast<ui::MouseMessage*>(msg);
2533 
2534     ColorPicker picker;
2535     picker.pickColor(getSite(),
2536                      screenToEditorF(mouseMsg ? mouseMsg->position():
2537                                                 ui::get_mouse_position()),
2538                      m_proj, ColorPicker::FromComposition);
2539     m_showGuidesThisCel = (picker.layer() ? picker.layer()->cel(m_frame):
2540                                             nullptr);
2541   }
2542   else {
2543     m_showGuidesThisCel = nullptr;
2544   }
2545 
2546   if (m_showGuidesThisCel != oldShowGuidesThisCel)
2547     invalidate();
2548 }
2549 
2550 // static
registerCommands()2551 void Editor::registerCommands()
2552 {
2553   Commands::instance()
2554     ->add(
2555       new QuickCommand(
2556         CommandId::SwitchNonactiveLayersOpacity(),
2557         []{
2558           static int oldValue = -1;
2559           auto& option = Preferences::instance().experimental.nonactiveLayersOpacity;
2560           if (oldValue == -1) {
2561             oldValue = option();
2562             if (option() == 255)
2563               option(128);
2564             else
2565               option(255);
2566           }
2567           else {
2568             const int newValue = oldValue;
2569             oldValue = option();
2570             option(newValue);
2571           }
2572           app_refresh_screen();
2573         }));
2574 }
2575 
2576 } // namespace app
2577