| 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 | |