Implement tool loop for selection tools#5222
Conversation
aseprite-bot
left a comment
There was a problem hiding this comment.
clang-tidy made some suggestions
| // Used by selection tools | ||
| virtual bool isSelectionToolLoop() const = 0; | ||
| virtual void addSelectionToolPoint(const gfx::Rect& rc) = 0; | ||
| virtual void clearSelectionToolMask(const bool finalStep) = 0; |
There was a problem hiding this comment.
warning: parameter 'finalStep' is const-qualified in the function declaration; const-qualification of parameters only has an effect in function definitions [readability-avoid-const-params-in-decls]
| virtual void clearSelectionToolMask(const bool finalStep) = 0; | |
| virtual void clearSelectionToolMask(bool finalStep) = 0; |
| void drawSpriteUnclippedRect(ui::Graphics* g, const gfx::Rect& rc); | ||
| void drawMaskSafe(); | ||
| void drawMask(ui::Graphics* g); | ||
| enum MaskIndex { Document, SelectionTool, Count }; |
There was a problem hiding this comment.
warning: enum 'MaskIndex' uses a larger base type ('unsigned int', size: 4 bytes) than necessary for its value set, consider using 'std::uint8_t' (1 byte) as the base type to reduce its size [performance-enum-size]
enum MaskIndex { Document, SelectionTool, Count };
^| if (isSelectionPreview && | ||
| (!m_document->isMaskVisible() || | ||
| (int(getModifiers()) & int(tools::ToolLoopModifiers::kReplaceSelection)) != 0)) { | ||
| Mask emptyMask; |
There was a problem hiding this comment.
warning: variable 'emptyMask' of type 'Mask' can be declared 'const' [misc-const-correctness]
| Mask emptyMask; | |
| Mask const emptyMask; |
| // Paint ink | ||
| if (m_ink->isPaint()) { | ||
| try { | ||
| ContextReader reader(m_context, 500); |
There was a problem hiding this comment.
warning: variable 'reader' of type 'ContextReader' can be declared 'const' [misc-const-correctness]
| ContextReader reader(m_context, 500); | |
| ContextReader const reader(m_context, 500); |
| if (m_ink->isPaint()) { | ||
| try { | ||
| ContextReader reader(m_context, 500); | ||
| ContextWriter writer(reader); |
There was a problem hiding this comment.
warning: variable 'writer' of type 'ContextWriter' can be declared 'const' [misc-const-correctness]
| ContextWriter writer(reader); | |
| ContextWriter const writer(reader); |
| // Start with an empty mask if the user is selecting with "default selection mode" | ||
| if (!m_document->isMaskVisible() || | ||
| (int(getModifiers()) & int(tools::ToolLoopModifiers::kReplaceSelection)) != 0) { | ||
| Mask emptyMask; |
There was a problem hiding this comment.
warning: variable 'emptyMask' of type 'Mask' can be declared 'const' [misc-const-correctness]
| Mask emptyMask; | |
| Mask const emptyMask; |
| const tools::Pointer::Button button, | ||
| const bool convertLineToFreehand, | ||
| const bool selectTiles); | ||
| const bool selectTiles, |
There was a problem hiding this comment.
warning: parameter 'selectTiles' is const-qualified in the function declaration; const-qualification of parameters only has an effect in function definitions [readability-avoid-const-params-in-decls]
| const bool selectTiles, | |
| bool selectTiles, |
| const bool convertLineToFreehand, | ||
| const bool selectTiles); | ||
| const bool selectTiles, | ||
| const bool selectionToolLoopEnabled); |
There was a problem hiding this comment.
warning: parameter 'selectionToolLoopEnabled' is const-qualified in the function declaration; const-qualification of parameters only has an effect in function definitions [readability-avoid-const-params-in-decls]
| const bool selectionToolLoopEnabled); | |
| bool selectionToolLoopEnabled); |
Hi @Liebranca, good first draft! 👍
As I said in the email, probably we can add a new experimental flag for this, and thinking a little more, probably we should make the option available in Edit > Preferences > Experimental, it's highly probable that some users might prefer to keep the current behavior anyway.
As a first implementation I'd use a static marching ant pattern with the same black & white (like the Anyway something important: We shouldn't use the ExpandCelCanvas in this new implementation, we should paint the feedback directly on the editor. ExpandCelCanvas creates a cel of the full sprite size and it's something we want to avoid for selection tools. Should improve the performance a lot when we select big images.
As we should remove the
I didn't see this in detail, I think
We can have this variable, probably this could be a static member variable shared between all editors (and nullptr by default, only valid when a
I think
At the moment (and probably for several versions) we will keep the old implementation working (so the user can disable this new experimental implementation, that if it works ok, should be enabled by default in future versions). |
aseprite-bot
left a comment
There was a problem hiding this comment.
clang-tidy made some suggestions
|
|
||
| void Editor::makeSelectionToolMask() | ||
| { | ||
| m_selectionToolMask.reset(new Mask()); |
There was a problem hiding this comment.
warning: method 'makeSelectionToolMask' can be made static [readability-convert-member-functions-to-static]
src/app/ui/editor/editor.h:320:
- void makeSelectionToolMask();
+ static void makeSelectionToolMask();|
|
||
| void Editor::deleteSelectionToolMask() | ||
| { | ||
| m_selectionToolMask.reset(); |
There was a problem hiding this comment.
warning: method 'deleteSelectionToolMask' can be made static [readability-convert-member-functions-to-static]
src/app/ui/editor/editor.h:321:
- void deleteSelectionToolMask();
+ static void deleteSelectionToolMask();|
|
||
| bool Editor::hasSelectionToolMask() | ||
| { | ||
| return m_selectionToolMask && !m_selectionToolMask->isEmpty(); |
There was a problem hiding this comment.
warning: method 'hasSelectionToolMask' can be made static [readability-convert-member-functions-to-static]
src/app/ui/editor/editor.h:322:
- bool hasSelectionToolMask();
+ static bool hasSelectionToolMask();| // an Editor or EditorState event. | ||
| void showUnhandledException(const std::exception& ex, const ui::Message* msg); | ||
|
|
||
| Mask* getSelectionToolMask() { return m_selectionToolMask.get(); } |
There was a problem hiding this comment.
warning: method 'getSelectionToolMask' can be made static [readability-convert-member-functions-to-static]
| Mask* getSelectionToolMask() { return m_selectionToolMask.get(); } | |
| static Mask* getSelectionToolMask() { return m_selectionToolMask.get(); } |
| return false; | ||
| } | ||
| else | ||
| return ToolLoopBase::needsCelCoordinates(); |
There was a problem hiding this comment.
warning: do not use 'else' after 'return' [readability-else-after-return]
| return ToolLoopBase::needsCelCoordinates(); | |
| return ToolLoopBase::needsCelCoordinates(); |
| Slice* slice = new Slice; | ||
| slice->setName(getUniqueSliceName()); | ||
|
|
||
| SliceKey key(bounds); |
There was a problem hiding this comment.
warning: variable 'key' of type 'SliceKey' can be declared 'const' [misc-const-correctness]
| SliceKey key(bounds); | |
| SliceKey const key(bounds); |
|
|
||
| private: | ||
| // EditorObserver impl | ||
| void onScrollChanged(Editor* editor) override { updateAllVisibleRegion(); } |
There was a problem hiding this comment.
warning: parameter 'editor' is unused [misc-unused-parameters]
| void onScrollChanged(Editor* editor) override { updateAllVisibleRegion(); } | |
| void onScrollChanged(Editor* /*editor*/) override { updateAllVisibleRegion(); } |
| private: | ||
| // EditorObserver impl | ||
| void onScrollChanged(Editor* editor) override { updateAllVisibleRegion(); } | ||
| void onZoomChanged(Editor* editor) override { updateAllVisibleRegion(); } |
There was a problem hiding this comment.
warning: parameter 'editor' is unused [misc-unused-parameters]
| void onZoomChanged(Editor* editor) override { updateAllVisibleRegion(); } | |
| void onZoomChanged(Editor* /*editor*/) override { updateAllVisibleRegion(); } |
| void onScrollChanged(Editor* editor) override { updateAllVisibleRegion(); } | ||
| void onZoomChanged(Editor* editor) override { updateAllVisibleRegion(); } | ||
|
|
||
| std::string getUniqueSliceName() const |
There was a problem hiding this comment.
warning: method 'getUniqueSliceName' can be made static [readability-convert-member-functions-to-static]
| std::string getUniqueSliceName() const | |
| static std::string getUniqueSliceName() |
| std::string getUniqueSliceName() const | ||
| { | ||
| std::string prefix = "Slice"; | ||
| int max = 0; |
There was a problem hiding this comment.
warning: variable 'max' of type 'int' can be declared 'const' [misc-const-correctness]
| int max = 0; | |
| int const max = 0; |
|
I think it's a pretty good first implementation to go forward. I'll merge it as it is in the |
* Added a new Editor::drawMaskBoundaries() and Editor::m_selectionToolMaskBoundaries to store the generated boundaries for the active SelectionToolLoopImpl. * Removed app::Doc::setMaskBoundaries() used to restore the active mask boundaries. * Removed Editor::MaskIndex
This adds a new tool loop for use with selection tools (and slice, too): it simply makes it so one can see their strokes no matter the zoom level. Fix #2852.
For testing purposes (comparing the old tool loop to the new one), I've added a bool to toggle between them: holding down the keys for a 'change brush size' action while in standby state with a selection tool will enable the old implementation, and 'deselect all' re-enables the new one.
I am unsure whether it would be best to use a solid color for drawing the mask or a marching ants pattern, and which color or colors to use for it. There are many possibilities with this, could be worth it to make it customizable later on. I was tempted to make the colors depend on whether one is adding, subtracting, replacing or intersecting, but I held off on it as it seemed like too much.
Anyway, for now I've made it so the new implementation looks almost identical to the original, so as to not overcomplicate things. I've left a few additional switches to quickly swap between some alternatives, again just for testing purposes:
kReplaceSelectionmodifier (default), the mask is drawn over the ink as a solid black, so it almost looks the same as the old implementation.kAddSelectionmodifier, only the mask is used.kSubtractSelectionmodifier, the mask is drawn over the ink using a marching ants pattern.kIntersectSelectionmodifier, only the mask is used, and drawk with a marching ants pattern.Side note: I think that for the defaut case the mask could be disabled when zoomed in, as it's not necessary, and activated only when zoomed out and the ink becomes less visible. I didn't add this just to keep things simple, but it's fairly easy to do.
Finally, there's only a few minor details left to handle:
updateEditor(), but I can tell that's not quite right. Needless to say, this is actually unnoticeable if the mask is the same color as the edges.m_selectionToolMaskmember variable, but maybe there's a more appropriate place for it.