Remove m_expandCelCanvas from SelectionToolLoopImpl

This commit is contained in:
Liebranca 2025-06-20 01:31:22 -03:00
parent b1d42a24f8
commit b2afbf91d5
11 changed files with 144 additions and 124 deletions

View File

@ -255,6 +255,7 @@
<option id="one_finger_as_mouse_movement" type="bool" default="true" />
<option id="load_wintab_driver" type="bool" default="false" />
<option id="flash_layer" type="bool" default="false" />
<option id="use_selection_tool_loop" type="bool" default="false" />
<option id="nonactive_layers_opacity" type="int" default="255" />
<option id="nonactive_layers_opacity_preview" type="int" default="255" />
</section>

View File

@ -1555,6 +1555,7 @@ set_cursor_fix = Set cursor position from stylus location
set_cursor_fix_tooltip = Sets the mouse position to the pen location when\nyou have two pointers available (e.g. mouse and pen)\n\nUseful to zoom in/out from the pen position and to get the\ncorrect cursor location when screencasting/live streaming.
wintab_more_info = (More Information)
flash_selected_layer = Flash layer when it is selected
use_selection_tool_loop = New selection tools implementation
non_active_layer_opacity = Opacity for non-active layers:
cel_content_format = Cel content format:
cel_format_compressed = Compressed

View File

@ -619,6 +619,7 @@
text="@.hue_with_sat_value"
pref="experimental.hue_with_sat_value_for_color_selector" />
<check id="flash_layer" text="@.flash_selected_layer" />
<check id="use_selection_tool_loop" text="@.use_selection_tool_loop" />
<hbox>
<label text="@.non_active_layer_opacity" />
<slider id="nonactive_layers_opacity" min="0" max="255" width="128" />

View File

@ -636,6 +636,7 @@ public:
}
#endif
useSelectionToolLoop()->setSelected(m_pref.experimental.useSelectionToolLoop());
flashLayer()->setSelected(m_pref.experimental.flashLayer());
nonactiveLayersOpacity()->setValue(m_pref.experimental.nonactiveLayersOpacity());
@ -867,6 +868,7 @@ public:
m_pref.asepriteFormat.celFormat(gen::CelContentFormat(celFormat()->getSelectedItemIndex()));
// Experimental features
m_pref.experimental.useSelectionToolLoop(useSelectionToolLoop()->isSelected());
m_pref.experimental.flashLayer(flashLayer()->isSelected());
m_pref.experimental.nonactiveLayersOpacity(nonactiveLayersOpacity()->getValue());
m_pref.quantization.rgbmapAlgorithm(m_rgbmapAlgorithmSelector.algorithm());

View File

@ -291,13 +291,12 @@ public:
if (m_createSlice)
m_maxBounds |= rc;
else {
rc &= loop->getDstImage()->bounds();
if (loop->isSelectionToolLoop())
if (loop->isSelectionToolLoop()) {
rc &= loop->sprite()->bounds();
loop->addSelectionToolPoint(rc);
// NOTE: this condition is here for drawing mode switches, remove/rework after testing
if (!loop->isSelectionToolLoop() ||
(loop->getPointShape()->isFloodFill() || !loop->getPreviewFilled())) {
}
else {
rc &= loop->getDstImage()->bounds();
for (int v = rc.y; v < rc.y2(); ++v)
BaseInk::inkHline(rc.x, v, rc.x2() - 1, loop);
}
@ -311,7 +310,8 @@ public:
m_maxBounds = gfx::Rect(0, 0, 0, 0);
}
else {
m_maxBounds &= loop->getDstImage()->bounds();
m_maxBounds &= (loop->isSelectionToolLoop() ? loop->sprite()->bounds() :
loop->getDstImage()->bounds());
loop->onSliceRect(m_maxBounds);
}
}
@ -463,13 +463,12 @@ public:
m_maxBounds |= rc;
}
else {
rc &= loop->getDstImage()->bounds();
if (loop->isSelectionToolLoop())
if (loop->isSelectionToolLoop()) {
rc &= loop->sprite()->bounds();
loop->addSelectionToolPoint(rc);
// NOTE: this condition is here for drawing mode switches, remove/rework after testing
if (!loop->isSelectionToolLoop() ||
(loop->getPointShape()->isFloodFill() || !loop->getPreviewFilled())) {
}
else {
rc &= loop->getDstImage()->bounds();
for (int v = rc.y; v < rc.y2(); ++v)
BaseInk::inkHline(rc.x, v, rc.x2() - 1, loop);
}

View File

@ -29,8 +29,10 @@ void PointShape::doInkHline(int x1, int y, int x2, ToolLoop* loop)
{
Ink* ink = loop->getInk();
TiledMode tiledMode = loop->getTiledMode();
const int dstw = loop->getDstImage()->width();
const int dsth = loop->getDstImage()->height();
const int dstw = (!loop->isSelectionToolLoop() ? loop->getDstImage()->width() :
loop->sprite()->bounds().w);
const int dsth = (!loop->isSelectionToolLoop() ? loop->getDstImage()->height() :
loop->sprite()->bounds().h);
int x, w, size; // width or height
// In case the ink needs original cel coordinates, we have to

View File

@ -133,6 +133,7 @@ private:
// static
Editor* Editor::m_activeEditor = nullptr;
std::unique_ptr<Mask> Editor::m_selectionToolMask = nullptr;
// static
std::unique_ptr<EditorRender> Editor::m_renderEngine = nullptr;
@ -205,7 +206,6 @@ Editor::Editor(Doc* document, EditorFlags flags, EditorStatePtr state)
m_symmetryModeConn = Preferences::instance().symmetryMode.enabled.AfterChange.connect(
[this] { invalidateIfActive(); });
m_showExtrasConn = m_docPref.show.AfterChange.connect([this] { onShowExtrasChange(); });
m_selectionToolMask = std::unique_ptr<Mask>(new Mask());
m_document->add_observer(this);
@ -877,6 +877,13 @@ void Editor::drawSpriteUnclippedRect(ui::Graphics* g, const gfx::Rect& _rc)
m_proj.applyY(m_sprite->height()));
gfx::Rect enclosingRect = spriteRect;
// Redraw the background when the selection tool mask draws over it
static bool redrawBackground = false;
if (redrawBackground) {
drawBackground(g);
redrawBackground = false;
}
// Draw the main sprite at the center.
drawOneSpriteUnclippedRect(g, rc, 0, 0);
@ -1016,7 +1023,7 @@ void Editor::drawSpriteUnclippedRect(ui::Graphics* g, const gfx::Rect& _rc)
if (haveSegs)
drawMask(g, MaskIndex::Document);
if (!m_selectionToolMask->isEmpty()) {
if (hasSelectionToolMask()) {
const auto segs = m_document->maskBoundaries();
m_document->generateMaskBoundaries(m_selectionToolMask.get());
drawMask(g, MaskIndex::SelectionTool);
@ -1025,6 +1032,10 @@ void Editor::drawSpriteUnclippedRect(ui::Graphics* g, const gfx::Rect& _rc)
m_document->setMaskBoundaries(segs);
else
m_document->destroyMaskBoundaries();
const gfx::Point prevPoint(m_selectionToolMask->bounds().point2());
if (prevPoint.x >= m_sprite->width() || prevPoint.y >= m_sprite->height())
redrawBackground = true;
}
// Post-render decorator.
@ -1085,17 +1096,7 @@ void Editor::drawMask(Graphics* g, const MaskIndex index)
gfx::rgba(255, 255, 255, 255));
}
else {
// NOTE: this condition is here for drawing mode switches, remove/rework after testing
if ((int(getToolLoopModifiers()) & (int(tools::ToolLoopModifiers::kSubtractSelection) |
int(tools::ToolLoopModifiers::kIntersectSelection))) != 0) {
set_checkered_paint_mode(paint,
m_antsOffset,
gfx::rgba(0, 32, 64, 255),
gfx::rgba(0, 128, 255, 255));
}
else {
paint.color(gfx::rgba(0, 0, 0, 255));
}
set_checkered_paint_mode(paint, 0, gfx::rgba(0, 0, 0, 255), gfx::rgba(255, 255, 255, 255));
}
// We translate the path instead of applying a matrix to the
@ -1108,12 +1109,11 @@ void Editor::drawMask(Graphics* g, const MaskIndex index)
void Editor::drawMaskSafe()
{
if (((m_flags & kShowMask) == 0 && !m_selectionToolMask->isEmpty()) ||
!(isVisible() && m_document))
if (((m_flags & kShowMask) == 0 && !hasSelectionToolMask()) || !(isVisible() && m_document))
return;
const bool haveSegs = m_document->hasMaskBoundaries();
if (haveSegs || !m_selectionToolMask->isEmpty()) {
if (haveSegs || hasSelectionToolMask()) {
Region region;
getDrawableRegion(region, kCutTopWindows);
region.offset(-bounds().origin());
@ -1129,7 +1129,7 @@ void Editor::drawMaskSafe()
}
}
if (!m_selectionToolMask->isEmpty()) {
if (hasSelectionToolMask()) {
const auto segs = m_document->maskBoundaries();
m_document->generateMaskBoundaries(m_selectionToolMask.get());
for (const gfx::Rect& rc : region) {
@ -2040,6 +2040,21 @@ void Editor::showUnhandledException(const std::exception& ex, const ui::Message*
(state ? typeid(*state).name() : "None"));
}
void Editor::makeSelectionToolMask()
{
m_selectionToolMask.reset(new Mask());
}
void Editor::deleteSelectionToolMask()
{
m_selectionToolMask.reset();
}
bool Editor::hasSelectionToolMask()
{
return m_selectionToolMask && !m_selectionToolMask->isEmpty();
}
//////////////////////////////////////////////////////////////////////
// Message handler for the editor
@ -2426,9 +2441,6 @@ void Editor::onPaint(ui::PaintEvent& ev)
drawMask(g, MaskIndex::Document);
m_antsTimer.start();
}
else if (!m_selectionToolMask->isEmpty()) {
m_antsTimer.start();
}
else {
m_antsTimer.stop();
}

View File

@ -317,8 +317,12 @@ public:
// an Editor or EditorState event.
void showUnhandledException(const std::exception& ex, const ui::Message* msg);
static void registerCommands();
Mask* getSelectionToolMask() { return m_selectionToolMask.get(); }
void makeSelectionToolMask();
void deleteSelectionToolMask();
bool hasSelectionToolMask();
static void registerCommands();
protected:
bool onProcessMessage(ui::Message* msg) override;
@ -508,7 +512,7 @@ private:
static std::unique_ptr<EditorRender> m_renderEngine;
// Used for selection tool feedback
std::unique_ptr<Mask> m_selectionToolMask;
static std::unique_ptr<Mask> m_selectionToolMask;
};
} // namespace app

View File

@ -458,10 +458,8 @@ bool StandbyState::onSetCursor(Editor* editor, const gfx::Point& mouseScreenPos)
return StateWithWheelBehavior::onSetCursor(editor, mouseScreenPos);
}
static bool selectionToolLoopEnabled = true;
bool StandbyState::onKeyDown(Editor* editor, KeyMessage* msg)
{
selectionToolLoopEnabled = true;
if (Preferences::instance().editor.straightLinePreview() &&
checkStartDrawingStraightLine(editor, nullptr, nullptr))
return false;
@ -473,8 +471,6 @@ bool StandbyState::onKeyDown(Editor* editor, KeyMessage* msg)
// in a selection-like tool
if (keys.size() == 1 && keys[0]->wheelAction() == WheelAction::BrushSize &&
editor->getCurrentEditorInk()->isSelection()) {
// TEST: disable new tool loop implementation for selection tools
selectionToolLoopEnabled = false;
return false;
}
@ -624,8 +620,7 @@ DrawingState* StandbyState::startDrawingState(Editor* editor,
UIContext::instance(),
pointer.button(),
(drawingType == DrawingType::LineFreehand),
(drawingType == DrawingType::SelectTiles),
selectionToolLoopEnabled);
(drawingType == DrawingType::SelectTiles));
if (!toolLoop)
return nullptr;

View File

@ -459,7 +459,6 @@ protected:
gfx::Point m_maskOrigin;
bool m_internalCancel = false;
Tx m_tx;
std::unique_ptr<ExpandCelCanvas> m_expandCelCanvas;
Image* m_floodfillSrcImage;
bool m_saveLastPoint;
@ -489,18 +488,12 @@ public:
{
if (m_editor)
m_editor->remove_observer(this);
// getSrcImage() is a virtual member function but ToolLoopImpl is
// marked as final to avoid not calling a derived version from
// this destructor.
if (m_floodfillSrcImage != getSrcImage())
delete m_floodfillSrcImage;
}
void constructor_prologue()
{
if (m_pointShape->isFloodFill()) {
if (m_tilesMode) {
if (m_tilesMode && !isSelectionToolLoop()) {
// This will be set later to getSrcImage()
m_floodfillSrcImage = nullptr;
}
@ -519,16 +512,17 @@ public:
else if (Cel* cel = m_layer->cel(m_frame)) {
m_floodfillSrcImage = render::rasterize_with_sprite_bounds(cel);
}
else if (isSelectionToolLoop() && !m_layer->cel(m_frame)) {
m_floodfillSrcImage =
Image::create(m_sprite->pixelFormat(), m_sprite->width(), m_sprite->height());
m_floodfillSrcImage->clear(m_sprite->transparentColor());
}
}
}
void constructor_epilogue()
{
// Setup the new grid of ExpandCelCanvas which can be displaced to
// match the new temporal cel position (m_celOrigin).
m_grid = m_expandCelCanvas->getGrid();
m_celOrigin = m_expandCelCanvas->getCelOrigin();
m_mask = m_document->mask();
m_maskOrigin = (!m_mask->isEmpty() ? gfx::Point(m_mask->bounds().x - m_celOrigin.x,
m_mask->bounds().y - m_celOrigin.y) :
@ -551,46 +545,7 @@ public:
return ToolLoopBase::needsCelCoordinates();
}
void rollback() override
{
try {
ContextReader reader(m_context, 500);
ContextWriter writer(reader);
m_expandCelCanvas->rollback();
}
catch (const LockedDocException& ex) {
Console::showException(ex);
}
update_screen_for_document(m_document);
}
const Cel* getCel() override { return m_expandCelCanvas->getCel(); }
const Image* getSrcImage() override { return m_expandCelCanvas->getSourceCanvas(); }
const Image* getFloodFillSrcImage() override { return m_floodfillSrcImage; }
Image* getDstImage() override { return m_expandCelCanvas->getDestCanvas(); }
Tileset* getDstTileset() override { return m_expandCelCanvas->getDestTileset(); }
void validateSrcImage(const gfx::Region& rgn) override
{
m_expandCelCanvas->validateSourceCanvas(rgn);
}
void validateDstImage(const gfx::Region& rgn) override
{
m_expandCelCanvas->validateDestCanvas(rgn);
}
void validateDstTileset(const gfx::Region& rgn) override
{
m_expandCelCanvas->validateDestTileset(rgn, getIntertwine()->forceTilemapRegionToValidate());
}
void invalidateDstImage() override { m_expandCelCanvas->invalidateDestCanvas(); }
void invalidateDstImage(const gfx::Region& rgn) override
{
m_expandCelCanvas->invalidateDestCanvas(rgn);
}
void copyValidDstToSrcImage(const gfx::Region& rgn) override
{
m_expandCelCanvas->copyValidDestToSourceCanvas(rgn);
}
Mask* getMask() override { return m_mask; }
void setMask(Mask* newMask) override { m_tx(new cmd::SetMask(m_document, newMask)); }
gfx::Point getMaskOrigin() override { return m_maskOrigin; }
@ -704,9 +659,23 @@ public:
m_tx(new cmd::SetMask(m_document, &emptyMask));
}
// Setup the new grid of ExpandCelCanvas which can be displaced to
// match the new temporal cel position (m_celOrigin).
m_grid = m_expandCelCanvas->getGrid();
m_celOrigin = m_expandCelCanvas->getCelOrigin();
constructor_epilogue();
}
~ToolLoopImpl()
{
// getSrcImage() is a virtual member function but ToolLoopImpl is
// marked as final to avoid not calling a derived version from
// this destructor.
if (m_floodfillSrcImage != getSrcImage())
delete m_floodfillSrcImage;
}
// IToolLoop interface
void commit() override
{
@ -752,6 +721,45 @@ public:
update_screen_for_document(m_document);
}
void rollback() override
{
try {
ContextReader reader(m_context, 500);
ContextWriter writer(reader);
m_expandCelCanvas->rollback();
}
catch (const LockedDocException& ex) {
Console::showException(ex);
}
update_screen_for_document(m_document);
}
const Cel* getCel() override { return m_expandCelCanvas->getCel(); }
const Image* getSrcImage() override { return m_expandCelCanvas->getSourceCanvas(); }
Image* getDstImage() override { return m_expandCelCanvas->getDestCanvas(); }
Tileset* getDstTileset() override { return m_expandCelCanvas->getDestTileset(); }
void validateSrcImage(const gfx::Region& rgn) override
{
m_expandCelCanvas->validateSourceCanvas(rgn);
}
void validateDstImage(const gfx::Region& rgn) override
{
m_expandCelCanvas->validateDestCanvas(rgn);
}
void validateDstTileset(const gfx::Region& rgn) override
{
m_expandCelCanvas->validateDestTileset(rgn, getIntertwine()->forceTilemapRegionToValidate());
}
void invalidateDstImage() override { m_expandCelCanvas->invalidateDestCanvas(); }
void invalidateDstImage(const gfx::Region& rgn) override
{
m_expandCelCanvas->invalidateDestCanvas(rgn);
}
void copyValidDstToSrcImage(const gfx::Region& rgn) override
{
m_expandCelCanvas->copyValidDestToSourceCanvas(rgn);
}
bool useMask() override { return m_useMask; }
bool getFilled() override { return m_filled; }
bool getPreviewFilled() override { return m_previewFilled; }
@ -764,6 +772,7 @@ private:
int m_sprayWidth;
int m_spraySpeed;
bool m_useMask;
std::unique_ptr<ExpandCelCanvas> m_expandCelCanvas;
};
//////////////////////////////////////////////////////////////////////
@ -781,21 +790,6 @@ public:
{
constructor_prologue();
// 'isSelectionPreview = true' if the intention is to show a preview
// of Selection tools or Slice tool.
m_expandCelCanvas.reset(new ExpandCelCanvas(
site,
m_layer,
m_docPref.tiled.mode(),
m_tx,
ExpandCelCanvas::Flags(
ExpandCelCanvas::NeedsSource |
(m_layer->isTilemap() ? ExpandCelCanvas::PixelsBounds : ExpandCelCanvas::None) |
ExpandCelCanvas::SelectionPreview)));
if (!m_floodfillSrcImage)
m_floodfillSrcImage = const_cast<Image*>(getSrcImage());
// 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) {
@ -803,9 +797,16 @@ public:
m_tx(new cmd::SetMask(m_document, &emptyMask));
}
m_editor->makeSelectionToolMask();
constructor_epilogue();
}
~SelectionToolLoopImpl()
{
m_editor->deleteSelectionToolMask();
delete m_floodfillSrcImage;
}
// For drawing the selection to second mask
bool isSelectionToolLoop() const override { return true; }
void addSelectionToolPoint(const gfx::Rect& rc) override
@ -847,18 +848,21 @@ public:
if (redraw)
update_screen_for_document(m_document);
}
void rollback() override {}
const Image* getSrcImage() override { return m_floodfillSrcImage; }
Image* getDstImage() override { return nullptr; }
Tileset* getDstTileset() override { return nullptr; }
void validateSrcImage(const gfx::Region& rgn) override {}
void validateDstImage(const gfx::Region& rgn) override {}
void validateDstTileset(const gfx::Region& rgn) override {}
void invalidateDstImage() override {}
void invalidateDstImage(const gfx::Region& rgn) override {}
void copyValidDstToSrcImage(const gfx::Region& rgn) override {}
bool useMask() override { return false; }
bool getFilled() override { return true; }
bool getPreviewFilled() override
{
// NOTE: this condition is here for drawing mode switches, remove/rework after testing
if ((int(getModifiers()) & (int(tools::ToolLoopModifiers::kAddSelection) |
int(tools::ToolLoopModifiers::kIntersectSelection))) != 0) {
return getTracePolicy() == tools::TracePolicy::Last;
}
return false;
}
bool getPreviewFilled() override { return getTracePolicy() == tools::TracePolicy::Last; }
int getSprayWidth() override { return 0; }
int getSpraySpeed() override { return 0; }
};
@ -878,8 +882,7 @@ tools::ToolLoop* create_tool_loop(Editor* editor,
Context* context,
const tools::Pointer::Button button,
const bool convertLineToFreehand,
const bool selectTiles,
const bool selectionToolLoopEnabled)
const bool selectTiles)
{
Site site = editor->getSite();
doc::Grid grid = site.grid();
@ -984,7 +987,8 @@ tools::ToolLoop* create_tool_loop(Editor* editor,
fill_toolloop_params_from_tool_preferences(params);
ASSERT(context->activeDocument() == editor->document());
if (selectionToolLoopEnabled && (params.ink->isSelection() || params.ink->isSlice())) {
if (Preferences::instance().experimental.useSelectionToolLoop() &&
(params.ink->isSelection() || params.ink->isSlice())) {
auto* toolLoop =
new SelectionToolLoopImpl(editor, site, grid, context, params, saveLastPoint);
if (selectTiles)

View File

@ -58,8 +58,7 @@ tools::ToolLoop* create_tool_loop(Editor* editor,
Context* context,
const tools::Pointer::Button button,
const bool convertLineToFreehand,
const bool selectTiles,
const bool selectionToolLoopEnabled);
const bool selectTiles);
tools::ToolLoop* create_tool_loop_preview(Editor* editor,
const doc::BrushRef& brush,