1 | // Aseprite |
2 | // Copyright (C) 2018-2022 Igara Studio S.A. |
3 | // Copyright (C) 2001-2018 David Capello |
4 | // |
5 | // This program is distributed under the terms of |
6 | // the End-User License Agreement for Aseprite. |
7 | |
8 | #ifndef APP_UI_EDITOR_H_INCLUDED |
9 | #define APP_UI_EDITOR_H_INCLUDED |
10 | #pragma once |
11 | |
12 | #include "app/color.h" |
13 | #include "app/doc.h" |
14 | #include "app/doc_observer.h" |
15 | #include "app/pref/preferences.h" |
16 | #include "app/tools/active_tool_observer.h" |
17 | #include "app/tools/tool_loop_modifiers.h" |
18 | #include "app/ui/color_source.h" |
19 | #include "app/ui/editor/brush_preview.h" |
20 | #include "app/ui/editor/editor_hit.h" |
21 | #include "app/ui/editor/editor_observers.h" |
22 | #include "app/ui/editor/editor_state.h" |
23 | #include "app/ui/editor/editor_states_history.h" |
24 | #include "app/ui/tile_source.h" |
25 | #include "app/util/tiled_mode.h" |
26 | #include "doc/algorithm/flip_type.h" |
27 | #include "doc/frame.h" |
28 | #include "doc/image_buffer.h" |
29 | #include "doc/selected_objects.h" |
30 | #include "filters/tiled_mode.h" |
31 | #include "gfx/fwd.h" |
32 | #include "obs/connection.h" |
33 | #include "os/color_space.h" |
34 | #include "render/projection.h" |
35 | #include "render/zoom.h" |
36 | #include "ui/base.h" |
37 | #include "ui/cursor_type.h" |
38 | #include "ui/pointer_type.h" |
39 | #include "ui/timer.h" |
40 | #include "ui/widget.h" |
41 | |
42 | #include <memory> |
43 | #include <set> |
44 | |
45 | namespace doc { |
46 | class Layer; |
47 | class Sprite; |
48 | } |
49 | namespace gfx { |
50 | class Region; |
51 | } |
52 | namespace ui { |
53 | class Cursor; |
54 | class Graphics; |
55 | class View; |
56 | } |
57 | |
58 | namespace app { |
59 | class Context; |
60 | class DocView; |
61 | class EditorCustomizationDelegate; |
62 | class EditorRender; |
63 | class PixelsMovement; |
64 | class Site; |
65 | class Transformation; |
66 | |
67 | namespace tools { |
68 | class Ink; |
69 | class Pointer; |
70 | class Tool; |
71 | } |
72 | |
73 | enum class AutoScroll { |
74 | MouseDir, |
75 | ScrollDir, |
76 | }; |
77 | |
78 | class Editor : public ui::Widget, |
79 | public app::DocObserver, |
80 | public IColorSource, |
81 | public ITileSource, |
82 | public tools::ActiveToolObserver { |
83 | public: |
84 | enum EditorFlags { |
85 | kNoneFlag = 0, |
86 | kShowGrid = 1, |
87 | kShowMask = 2, |
88 | kShowOnionskin = 4, |
89 | kShowOutside = 8, |
90 | kShowDecorators = 16, |
91 | kShowSymmetryLine = 32, |
92 | kShowSlices = 64, |
93 | kUseNonactiveLayersOpacityWhenEnabled = 128, |
94 | kDefaultEditorFlags = (kShowGrid | |
95 | kShowMask | |
96 | kShowOnionskin | |
97 | kShowOutside | |
98 | kShowDecorators | |
99 | kShowSymmetryLine | |
100 | kShowSlices | |
101 | kUseNonactiveLayersOpacityWhenEnabled) |
102 | }; |
103 | |
104 | enum class ZoomBehavior { |
105 | CENTER, // Zoom from center (don't change center of the editor) |
106 | MOUSE, // Zoom from cursor |
107 | }; |
108 | |
109 | static ui::WidgetType Type(); |
110 | |
111 | Editor(Doc* document, |
112 | EditorFlags flags = kDefaultEditorFlags, |
113 | EditorStatePtr state = nullptr); |
114 | ~Editor(); |
115 | |
116 | static void destroyEditorSharedInternals(); |
117 | |
118 | bool isActive() const; |
119 | bool isUsingNewRenderEngine() const; |
120 | |
121 | DocView* getDocView() { return m_docView; } |
122 | void setDocView(DocView* docView) { m_docView = docView; } |
123 | |
124 | // Returns the current state. |
125 | EditorStatePtr getState() { return m_state; } |
126 | |
127 | bool isMovingPixels() const; |
128 | void dropMovingPixels(); |
129 | |
130 | // Changes the state of the editor. |
131 | void setState(const EditorStatePtr& newState); |
132 | |
133 | // Backs to previous state. |
134 | void backToPreviousState(); |
135 | |
136 | // Gets/sets the current decorator. The decorator is not owned by |
137 | // the Editor, so it must be deleted by the caller. |
138 | EditorDecorator* decorator() { return m_decorator; } |
139 | void setDecorator(EditorDecorator* decorator) { m_decorator = decorator; } |
140 | void getInvalidDecoratoredRegion(gfx::Region& region); |
141 | |
142 | EditorFlags editorFlags() const { return m_flags; } |
143 | void setEditorFlags(EditorFlags flags) { m_flags = flags; } |
144 | |
145 | bool () const { |
146 | return m_flashing != Flashing::None; |
147 | } |
148 | |
149 | Doc* document() { return m_document; } |
150 | Sprite* sprite() { return m_sprite; } |
151 | Layer* layer() { return m_layer; } |
152 | frame_t frame() { return m_frame; } |
153 | DocumentPreferences& docPref() { return m_docPref; } |
154 | |
155 | void getSite(Site* site) const; |
156 | Site getSite() const; |
157 | |
158 | void setLayer(const Layer* layer); |
159 | void setFrame(frame_t frame); |
160 | |
161 | const render::Projection& projection() const { return m_proj; } |
162 | const render::Zoom& zoom() const { return m_proj.zoom(); } |
163 | const gfx::Point& padding() const { return m_padding; } |
164 | |
165 | void setZoom(const render::Zoom& zoom); |
166 | void setDefaultScroll(); |
167 | void setScrollToCenter(); |
168 | void setScrollAndZoomToFitScreen(); |
169 | void setEditorScroll(const gfx::Point& scroll); |
170 | void setEditorZoom(const render::Zoom& zoom); |
171 | |
172 | // Updates the Editor's view. |
173 | void updateEditor(const bool restoreScrollPos); |
174 | |
175 | // Draws the sprite taking care of the whole clipping region. |
176 | void drawSpriteClipped(const gfx::Region& updateRegion); |
177 | |
178 | void flashCurrentLayer(); |
179 | |
180 | // Convert ui::Display coordinates (pixel relative to the top-left |
181 | // corner of the in the display content bounds) from/to |
182 | // editor/sprite coordinates (pixel in the canvas). |
183 | // |
184 | // TODO we should rename these functions to displayToEditor() and editorToDisplay() |
185 | gfx::Point screenToEditor(const gfx::Point& pt); |
186 | gfx::Point screenToEditorCeiling(const gfx::Point& pt); |
187 | gfx::PointF screenToEditorF(const gfx::Point& pt); |
188 | gfx::Point editorToScreen(const gfx::Point& pt); |
189 | gfx::PointF editorToScreenF(const gfx::PointF& pt); |
190 | gfx::Rect screenToEditor(const gfx::Rect& rc); |
191 | gfx::Rect editorToScreen(const gfx::Rect& rc); |
192 | gfx::RectF editorToScreenF(const gfx::RectF& rc); |
193 | |
194 | void add_observer(EditorObserver* observer); |
195 | void remove_observer(EditorObserver* observer); |
196 | |
197 | void setCustomizationDelegate(EditorCustomizationDelegate* delegate); |
198 | |
199 | EditorCustomizationDelegate* getCustomizationDelegate() { |
200 | return m_customizationDelegate; |
201 | } |
202 | |
203 | // Returns the visible area of the viewport in sprite coordinates. |
204 | gfx::Rect getViewportBounds(); |
205 | |
206 | // Returns the visible area of the active sprite. |
207 | gfx::Rect getVisibleSpriteBounds(); |
208 | |
209 | gfx::Size canvasSize() const; |
210 | gfx::Point mainTilePosition() const; |
211 | void expandRegionByTiledMode(gfx::Region& rgn, |
212 | const bool withProj) const; |
213 | void collapseRegionByTiledMode(gfx::Region& rgn) const; |
214 | |
215 | // Changes the scroll to see the given point as the center of the editor. |
216 | void centerInSpritePoint(const gfx::Point& spritePos); |
217 | |
218 | void updateStatusBar(); |
219 | |
220 | // Control scroll when cursor goes out of the editor viewport. |
221 | gfx::Point autoScroll(const ui::MouseMessage* msg, |
222 | const AutoScroll dir); |
223 | |
224 | tools::Tool* getCurrentEditorTool() const; |
225 | tools::Ink* getCurrentEditorInk() const; |
226 | |
227 | tools::ToolLoopModifiers getToolLoopModifiers() const { return m_toolLoopModifiers; } |
228 | bool isAutoSelectLayer(); |
229 | |
230 | // Returns true if we are able to draw in the current doc/sprite/layer/cel. |
231 | bool canDraw(); |
232 | |
233 | // Returns true if the cursor is inside the active mask/selection. |
234 | bool isInsideSelection(); |
235 | |
236 | // Returns true if the cursor is inside the selection and the |
237 | // selection mode is the default one which prioritizes and easy |
238 | // way to move the selection. |
239 | bool canStartMovingSelectionPixels(); |
240 | |
241 | // Returns true if the range selected in the timeline should be |
242 | // kept. E.g. When we are moving/transforming pixels on multiple |
243 | // cels, the MovingPixelsState can handle previous/next frame |
244 | // commands, so it's nice to keep the timeline range intact while |
245 | // we are in the MovingPixelsState. |
246 | bool keepTimelineRange(); |
247 | |
248 | // Returns the element that will be modified if the mouse is used |
249 | // in the given position. |
250 | EditorHit calcHit(const gfx::Point& mouseScreenPos); |
251 | |
252 | void setZoomAndCenterInMouse(const render::Zoom& zoom, |
253 | const gfx::Point& mousePos, ZoomBehavior zoomBehavior); |
254 | |
255 | void pasteImage(const Image* image, const Mask* mask = nullptr); |
256 | |
257 | void startSelectionTransformation(const gfx::Point& move, double angle); |
258 | void startFlipTransformation(doc::algorithm::FlipType flipType); |
259 | void updateTransformation(const Transformation& transform); |
260 | |
261 | // Used by EditorView to notify changes in the view's scroll |
262 | // position. |
263 | void notifyScrollChanged(); |
264 | void notifyZoomChanged(); |
265 | |
266 | // Returns true and changes to ScrollingState when "msg" says "the |
267 | // user wants to scroll". Same for zoom. |
268 | bool checkForScroll(ui::MouseMessage* msg); |
269 | bool checkForZoom(ui::MouseMessage* msg); |
270 | |
271 | // Start Scrolling/ZoomingState |
272 | void startScrollingState(ui::MouseMessage* msg); |
273 | void startZoomingState(ui::MouseMessage* msg); |
274 | |
275 | // Animation control |
276 | void play(const bool playOnce, |
277 | const bool playAll); |
278 | void stop(); |
279 | bool isPlaying() const; |
280 | |
281 | // Shows a popup menu to change the editor animation speed. |
282 | void (Option<bool>& playOnce, |
283 | Option<bool>& playAll, |
284 | const bool withStopBehaviorOptions); |
285 | double getAnimationSpeedMultiplier() const; |
286 | void setAnimationSpeedMultiplier(double speed); |
287 | |
288 | // Functions to be used in EditorState::onSetCursor() |
289 | void showMouseCursor(ui::CursorType cursorType, |
290 | const ui::Cursor* cursor = nullptr); |
291 | void showBrushPreview(const gfx::Point& pos); |
292 | |
293 | // Gets the brush preview controller. |
294 | BrushPreview& brushPreview() { return m_brushPreview; } |
295 | |
296 | static EditorRender& renderEngine() { return *m_renderEngine; } |
297 | |
298 | // IColorSource |
299 | app::Color getColorByPosition(const gfx::Point& pos) override; |
300 | |
301 | // ITileSource |
302 | doc::tile_t getTileByPosition(const gfx::Point& pos) override; |
303 | |
304 | void setTagFocusBand(int value) { m_tagFocusBand = value; } |
305 | int tagFocusBand() const { return m_tagFocusBand; } |
306 | |
307 | // Returns true if the Shift key to draw straight lines with a |
308 | // freehand tool is pressed. |
309 | bool startStraightLineWithFreehandTool(const tools::Pointer* pointer); |
310 | |
311 | // Functions to handle the set of selected slices. |
312 | bool isSliceSelected(const doc::Slice* slice) const; |
313 | void clearSlicesSelection(); |
314 | void selectSlice(const doc::Slice* slice); |
315 | bool selectSliceBox(const gfx::Rect& box); |
316 | void selectAllSlices(); |
317 | bool hasSelectedSlices() const { return !m_selectedSlices.empty(); } |
318 | |
319 | // Called by DocView's InputChainElement::onCancel() impl when Esc |
320 | // key is pressed to cancel the active selection. |
321 | void cancelSelections(); |
322 | |
323 | // Properties to show information in the status bar |
324 | bool showAutoCelGuides() const { return m_showAutoCelGuides; } |
325 | |
326 | static void registerCommands(); |
327 | |
328 | protected: |
329 | bool onProcessMessage(ui::Message* msg) override; |
330 | void onSizeHint(ui::SizeHintEvent& ev) override; |
331 | void onResize(ui::ResizeEvent& ev) override; |
332 | void onPaint(ui::PaintEvent& ev) override; |
333 | void onInvalidateRegion(const gfx::Region& region) override; |
334 | void onSamplingChange(); |
335 | void onFgColorChange(); |
336 | void onContextBarBrushChange(); |
337 | void onTiledModeBeforeChange(); |
338 | void onTiledModeChange(); |
339 | void (); |
340 | |
341 | // DocObserver impl |
342 | void onColorSpaceChanged(DocEvent& ev) override; |
343 | void onExposeSpritePixels(DocEvent& ev) override; |
344 | void onSpritePixelRatioChanged(DocEvent& ev) override; |
345 | void onBeforeRemoveLayer(DocEvent& ev) override; |
346 | void onBeforeRemoveCel(DocEvent& ev) override; |
347 | void onAddTag(DocEvent& ev) override; |
348 | void onRemoveTag(DocEvent& ev) override; |
349 | void onRemoveSlice(DocEvent& ev) override; |
350 | |
351 | // ActiveToolObserver impl |
352 | void onActiveToolChange(tools::Tool* tool) override; |
353 | |
354 | private: |
355 | enum class Flashing { None, , WaitingDeferedPaint }; |
356 | |
357 | void setStateInternal(const EditorStatePtr& newState); |
358 | void updateQuicktool(); |
359 | void updateToolByTipProximity(ui::PointerType pointerType); |
360 | |
361 | // firstFromMouseDown=true when we call this function from the |
362 | // first MouseDown message (instead of KeyDown). |
363 | void updateToolLoopModifiersIndicators(const bool firstFromMouseDown = false); |
364 | |
365 | void drawBackground(ui::Graphics* g); |
366 | void drawSpriteUnclippedRect(ui::Graphics* g, const gfx::Rect& rc); |
367 | void drawMaskSafe(); |
368 | void drawMask(ui::Graphics* g); |
369 | void drawGrid(ui::Graphics* g, const gfx::Rect& spriteBounds, const gfx::Rect& gridBounds, |
370 | const app::Color& color, int alpha); |
371 | void drawSlices(ui::Graphics* g); |
372 | void drawTileNumbers(ui::Graphics* g, const Cel* cel); |
373 | void drawCelBounds(ui::Graphics* g, const Cel* cel, const gfx::Color color); |
374 | void drawCelGuides(ui::Graphics* g, const Cel* cel, const Cel* mouseCel); |
375 | void drawCelHGuide(ui::Graphics* g, |
376 | const int sprX1, const int sprX2, |
377 | const int scrX1, const int scrX2, const int scrY, |
378 | const gfx::Rect& scrCelBounds, const gfx::Rect& scrCmpBounds, |
379 | const int dottedX); |
380 | void drawCelVGuide(ui::Graphics* g, |
381 | const int sprY1, const int sprY2, |
382 | const int scrY1, const int scrY2, const int scrX, |
383 | const gfx::Rect& scrCelBounds, const gfx::Rect& scrCmpBounds, |
384 | const int dottedY); |
385 | gfx::Rect getCelScreenBounds(const Cel* cel); |
386 | |
387 | void setCursor(const gfx::Point& mouseDisplayPos); |
388 | |
389 | // Draws the specified portion of sprite in the editor. Warning: |
390 | // You should setup the clip of the screen before calling this |
391 | // routine. |
392 | void drawOneSpriteUnclippedRect(ui::Graphics* g, const gfx::Rect& rc, int dx, int dy); |
393 | |
394 | gfx::Point (const render::Projection& proj); |
395 | |
396 | void invalidateCanvas(); |
397 | void invalidateIfActive(); |
398 | void updateAutoCelGuides(ui::Message* msg); |
399 | |
400 | // Stack of states. The top element in the stack is the current state (m_state). |
401 | EditorStatesHistory m_statesHistory; |
402 | EditorStatesHistory m_deletedStates; |
403 | |
404 | // Current editor state (it can be shared between several editors to |
405 | // the same document). This member cannot be NULL. |
406 | EditorStatePtr m_state; |
407 | |
408 | // Current decorator (to draw extra UI elements). |
409 | EditorDecorator* m_decorator; |
410 | |
411 | Doc* m_document; // Active document in the editor |
412 | Sprite* m_sprite; // Active sprite in the editor |
413 | Layer* m_layer; // Active layer in the editor |
414 | frame_t m_frame; // Active frame in the editor |
415 | render::Projection m_proj; // Zoom/pixel ratio in the editor |
416 | DocumentPreferences& m_docPref; |
417 | // Helper functions affected by the current Tiled Mode. |
418 | app::TiledModeHelper m_tiledModeHelper; |
419 | |
420 | // Brush preview |
421 | BrushPreview m_brushPreview; |
422 | |
423 | tools::ToolLoopModifiers m_toolLoopModifiers; |
424 | |
425 | // Extra space around the sprite. |
426 | gfx::Point m_padding; |
427 | |
428 | // Marching ants stuff |
429 | ui::Timer m_antsTimer; |
430 | int m_antsOffset; |
431 | |
432 | obs::scoped_connection m_samplingChangeConn; |
433 | obs::scoped_connection m_fgColorChangeConn; |
434 | obs::scoped_connection m_contextBarBrushChangeConn; |
435 | obs::scoped_connection ; |
436 | |
437 | // Slots listeing document preferences. |
438 | obs::scoped_connection m_tiledConnBefore; |
439 | obs::scoped_connection m_tiledConn; |
440 | obs::scoped_connection m_gridConn; |
441 | obs::scoped_connection m_pixelGridConn; |
442 | obs::scoped_connection m_bgConn; |
443 | obs::scoped_connection m_onionskinConn; |
444 | obs::scoped_connection m_symmetryModeConn; |
445 | |
446 | EditorObservers m_observers; |
447 | |
448 | EditorCustomizationDelegate* m_customizationDelegate; |
449 | |
450 | // TODO This field shouldn't be here. It should be removed when |
451 | // editors.cpp are finally replaced with a fully funtional Workspace |
452 | // widget. |
453 | DocView* m_docView; |
454 | |
455 | gfx::Point m_oldPos; |
456 | |
457 | EditorFlags m_flags; |
458 | |
459 | bool m_secondaryButton; |
460 | Flashing m_flashing; |
461 | |
462 | // Animation speed multiplier. |
463 | double m_aniSpeed; |
464 | bool m_isPlaying; |
465 | |
466 | // The Cel that is above the mouse if the Ctrl (or Cmd) key is |
467 | // pressed (move key). |
468 | Cel* m_showGuidesThisCel; |
469 | bool m_showAutoCelGuides; |
470 | |
471 | // Focused tag band. Used by the Timeline to save/restore the |
472 | // focused tag band for each sprite/editor. |
473 | int m_tagFocusBand; |
474 | |
475 | // Used to restore scroll when the tiled mode is changed. |
476 | // TODO could we avoid one extra field just to do this? |
477 | gfx::Point m_oldMainTilePos; |
478 | |
479 | #if ENABLE_DEVMODE |
480 | gfx::Rect m_perfInfoBounds; |
481 | #endif |
482 | |
483 | // For slices |
484 | doc::SelectedObjects m_selectedSlices; |
485 | |
486 | // The render engine must be shared between all editors so when a |
487 | // DrawingState is being used in one editor, other editors for the |
488 | // same document can show the same preview image/stroke being drawn |
489 | // (search for Render::setPreviewImage()). |
490 | static std::unique_ptr<EditorRender> m_renderEngine; |
491 | }; |
492 | |
493 | } // namespace app |
494 | |
495 | #endif |
496 | |