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