diff --git a/app/src/importexportdialog.cpp b/app/src/importexportdialog.cpp index 76010e6215..f893f2d18b 100644 --- a/app/src/importexportdialog.cpp +++ b/app/src/importexportdialog.cpp @@ -27,6 +27,7 @@ ImportExportDialog::ImportExportDialog(QWidget* parent, Mode eMode, FileType eFi ui = new Ui::ImportExportDialog; ui->setupUi(this); + hidePreviewGroupBox(true); m_fileDialog = new FileDialog(this); connect(ui->browseButton, &QPushButton::clicked, this, &ImportExportDialog::browse); diff --git a/app/src/importimageseqdialog.cpp b/app/src/importimageseqdialog.cpp index 2a6708bb19..953bddfa5a 100644 --- a/app/src/importimageseqdialog.cpp +++ b/app/src/importimageseqdialog.cpp @@ -53,7 +53,6 @@ void ImportImageSeqDialog::setupLayout() { hideInstructionsLabel(true); - hidePreviewGroupBox(true); if (mFileType == FileType::GIF) { setWindowTitle(tr("Import Animated GIF")); @@ -70,6 +69,7 @@ void ImportImageSeqDialog::setupPredefinedLayout() setInstructionsLabel(tr("Select an image that matches the criteria: MyFile000.png, eg. Joe001.png \n" "The importer will search and find images matching the same criteria. You can see the result in the preview box below.")); hideOptionsGroupBox(true); + hidePreviewGroupBox(false); connect(this, &ImportImageSeqDialog::filePathsChanged, this, &ImportImageSeqDialog::updatePreviewList); } diff --git a/app/src/mainwindow2.cpp b/app/src/mainwindow2.cpp index 14c6fc12e5..041f16e775 100644 --- a/app/src/mainwindow2.cpp +++ b/app/src/mainwindow2.cpp @@ -43,6 +43,7 @@ GNU General Public License for more details. #include "layermanager.h" #include "toolmanager.h" #include "playbackmanager.h" +#include "selectionmanager.h" #include "soundmanager.h" #include "viewmanager.h" @@ -121,6 +122,7 @@ MainWindow2::MainWindow2(QWidget* parent) : readSettings(); updateZoomLabel(); + selectionChanged(); connect(mEditor, &Editor::needSave, this, &MainWindow2::autoSave); connect(mToolBox, &ToolBoxWidget::clearButtonClicked, mEditor, &Editor::clearCurrentFrame); @@ -131,7 +133,7 @@ MainWindow2::MainWindow2(QWidget* parent) : setWindowTitle(PENCIL_WINDOW_TITLE); - showPresetDialog(); + tryLoadPreset(); } MainWindow2::~MainWindow2() @@ -271,6 +273,7 @@ void MainWindow2::createMenus() connect(ui->actionCopy, &QAction::triggered, mEditor, &Editor::copy); connect(ui->actionPaste, &QAction::triggered, mEditor, &Editor::paste); connect(ui->actionClearFrame, &QAction::triggered, mEditor, &Editor::clearCurrentFrame); + connect(mEditor->select(), &SelectionManager::selectionChanged, this, &MainWindow2::selectionChanged); connect(ui->actionFlip_X, &QAction::triggered, mCommands, &ActionCommands::flipSelectionX); connect(ui->actionFlip_Y, &QAction::triggered, mCommands, &ActionCommands::flipSelectionY); connect(ui->actionPegbarAlignment, &QAction::triggered, this, &MainWindow2::openPegAlignDialog); @@ -316,7 +319,6 @@ void MainWindow2::createMenus() bindActionWithSetting(ui->actionOnionPrev, SETTING::PREV_ONION); bindActionWithSetting(ui->actionOnionNext, SETTING::NEXT_ONION); - bindActionWithSetting(ui->actionMultiLayerOnionSkin, SETTING::MULTILAYER_ONION); //--- Animation Menu --- PlaybackManager* pPlaybackManager = mEditor->playback(); @@ -434,6 +436,7 @@ void MainWindow2::clearRecentFilesList() if (!recentFilesList.isEmpty()) { mRecentFileMenu->clear(); + mRecentFileMenu->saveToDisk(); QMessageBox::information(this, nullptr, tr("\n\n You have successfully cleared the list"), QMessageBox::Ok); @@ -479,6 +482,12 @@ void MainWindow2::currentLayerChanged() } } +void MainWindow2::selectionChanged() +{ + bool somethingSelected = mEditor->select()->somethingSelected(); + ui->menuSelection->setEnabled(somethingSelected); +} + void MainWindow2::closeEvent(QCloseEvent* event) { if (m2ndCloseEvent) @@ -516,18 +525,7 @@ void MainWindow2::newDocument(bool force) } else if (maybeSave()) { - if (mEditor->preference()->isOn(SETTING::ASK_FOR_PRESET)) - { - showPresetDialog(); - } - else - { - int defaultPreset = mEditor->preference()->getInt(SETTING::DEFAULT_PRESET); - newObjectFromPresets(defaultPreset); - - setWindowTitle(PENCIL_WINDOW_TITLE); - updateSaveState(); - } + tryLoadPreset(); } } @@ -660,8 +658,12 @@ bool MainWindow2::openObject(QString strFilePath) QSettings settings(PENCIL2D, PENCIL2D); settings.setValue(LAST_PCLX_PATH, object->filePath()); - mRecentFileMenu->addRecentFile(object->filePath()); - mRecentFileMenu->saveToDisk(); + // Add to recent file list, but only if we are + if (!object->filePath().isEmpty()) + { + mRecentFileMenu->addRecentFile(object->filePath()); + mRecentFileMenu->saveToDisk(); + } setWindowTitle(object->filePath().prepend("[*]")); setWindowModified(false); @@ -1065,10 +1067,14 @@ bool MainWindow2::newObjectFromPresets(int presetIndex) } mEditor->setObject(object); object->setFilePath(QString()); + + setWindowTitle(PENCIL_WINDOW_TITLE); + updateSaveState(); + return true; } -void MainWindow2::showPresetDialog() +void MainWindow2::tryLoadPreset() { if (mEditor->preference()->isOn(SETTING::ASK_FOR_PRESET)) { @@ -1089,6 +1095,11 @@ void MainWindow2::showPresetDialog() }); presetDialog->open(); } + else + { + int defaultPreset = mEditor->preference()->getInt(SETTING::DEFAULT_PRESET); + newObjectFromPresets(defaultPreset); + } } void MainWindow2::readSettings() diff --git a/app/src/mainwindow2.h b/app/src/mainwindow2.h index 6ed5619e53..3f194e7111 100644 --- a/app/src/mainwindow2.h +++ b/app/src/mainwindow2.h @@ -70,6 +70,7 @@ public slots: void openPegAlignDialog(); void closePegAlignDialog(); void currentLayerChanged(); + void selectionChanged(); public: void newDocument(bool force = false); @@ -126,7 +127,7 @@ private slots: void setupKeyboardShortcuts(); void clearKeyboardShortcuts(); void updateZoomLabel(); - void showPresetDialog(); + void tryLoadPreset(); void openPalette(); void importPalette(); diff --git a/app/ui/mainwindow2.ui b/app/ui/mainwindow2.ui index a19c5b8bf5..2b4b8a70f0 100644 --- a/app/ui/mainwindow2.ui +++ b/app/ui/mainwindow2.ui @@ -49,7 +49,7 @@ 0 0 831 - 41 + 24 @@ -135,7 +135,6 @@ - @@ -825,7 +824,7 @@ true - MultiLayer Onion Skin + Multilayer Onion Skin diff --git a/core_lib/src/canvaspainter.cpp b/core_lib/src/canvaspainter.cpp index ba3bf57110..a8ffdb2cb1 100644 --- a/core_lib/src/canvaspainter.cpp +++ b/core_lib/src/canvaspainter.cpp @@ -586,9 +586,11 @@ void CanvasPainter::paintOverlayCenter(QPainter &painter) painter.setBrush(Qt::NoBrush); painter.setRenderHint(QPainter::Antialiasing, false); - int offset = OVERLAY_SAFE_CENTER_CROSS_SIZE; - painter.drawLine(rect.center().x()-offset, rect.center().y(), rect.center().x()+offset, rect.center().y()); - painter.drawLine(rect.center().x(), rect.center().y()-offset, rect.center().x(), rect.center().y()+offset); + QPoint offsetX(OVERLAY_SAFE_CENTER_CROSS_SIZE, 0), offsetY(0, OVERLAY_SAFE_CENTER_CROSS_SIZE); + painter.drawLine(rect.center(), rect.center() - offsetX); + painter.drawLine(rect.center(), rect.center() + offsetX); + painter.drawLine(rect.center(), rect.center() - offsetY); + painter.drawLine(rect.center(), rect.center() + offsetY); painter.restore(); } diff --git a/core_lib/src/interface/editor.cpp b/core_lib/src/interface/editor.cpp index 93ed28f7ca..95544eaa2e 100644 --- a/core_lib/src/interface/editor.cpp +++ b/core_lib/src/interface/editor.cpp @@ -371,7 +371,7 @@ void BackupBitmapElement::restore(Editor* editor) { Layer* layer = editor->object()->getLayer(this->layer); auto selectMan = editor->select(); - selectMan->setSelection(mySelection); + selectMan->setSelection(mySelection, true); selectMan->setTransformedSelectionRect(myTransformedSelection); selectMan->setTempTransformedSelectionRect(myTempTransformedSelection); selectMan->setRotation(rotationAngle); @@ -401,7 +401,7 @@ void BackupVectorElement::restore(Editor* editor) { Layer* layer = editor->object()->getLayer(this->layer); auto selectMan = editor->select(); - selectMan->setSelection(mySelection); + selectMan->setSelection(mySelection, false); selectMan->setTransformedSelectionRect(myTransformedSelection); selectMan->setTempTransformedSelectionRect(myTempTransformedSelection); selectMan->setRotation(rotationAngle); @@ -477,7 +477,7 @@ void Editor::undo() if (layer->type() == Layer::VECTOR) { VectorImage *vectorImage = static_cast(layer)->getVectorImageAtFrame(mFrame); vectorImage->calculateSelectionRect(); - select()->setSelection(vectorImage->getSelectionRect()); + select()->setSelection(vectorImage->getSelectionRect(), false); } emit updateBackup(); } @@ -584,15 +584,17 @@ void Editor::paste() } } auto pLayerBitmap = static_cast(layer); + mScribbleArea->handleDrawingOnEmptyFrame(); pLayerBitmap->getLastBitmapImageAtFrame(currentFrame(), 0)->paste(&tobePasted); // paste the clipboard } else if (layer->type() == Layer::VECTOR && clipboardVectorOk) { backup(tr("Paste")); deselectAll(); + mScribbleArea->handleDrawingOnEmptyFrame(); VectorImage* vectorImage = (static_cast(layer))->getLastVectorImageAtFrame(currentFrame(), 0); vectorImage->paste(g_clipboardVectorImage); // paste the clipboard - select()->setSelection(vectorImage->getSelectionRect()); + select()->setSelection(vectorImage->getSelectionRect(), false); } } mScribbleArea->updateCurrentFrame(); @@ -966,8 +968,7 @@ void Editor::selectAll() vectorImage->selectAll(); rect = vectorImage->getSelectionRect(); } - select()->setSelection(rect); - emit updateCurrentFrame(); + select()->setSelection(rect, false); } void Editor::deselectAll() diff --git a/core_lib/src/interface/recentfilemenu.cpp b/core_lib/src/interface/recentfilemenu.cpp index d6caaa2a65..113cdfda67 100644 --- a/core_lib/src/interface/recentfilemenu.cpp +++ b/core_lib/src/interface/recentfilemenu.cpp @@ -51,16 +51,16 @@ void RecentFileMenu::clear() mRecentFiles.clear(); mRecentActions.clear(); addAction(mEmptyAction); - saveToDisk(); } void RecentFileMenu::setRecentFiles(const QStringList& filenames) { clear(); + // Iterate in reverse because items are prepended to the list when first added for (auto filename = filenames.crbegin(); filename != filenames.crend(); filename++) { - if (*filename != "") + if (!filename->isEmpty()) { addRecentFile(*filename); } @@ -114,7 +114,10 @@ void RecentFileMenu::addRecentFile(QString filename) addAction(action); addAction(mClearSeparator); addAction(mClearAction); - QObject::connect(mClearAction, &QAction::triggered, this, &RecentFileMenu::clear); + QObject::connect(mClearAction, &QAction::triggered, [this] { + clear(); + saveToDisk(); + }); } else { diff --git a/core_lib/src/interface/scribblearea.cpp b/core_lib/src/interface/scribblearea.cpp index f6cbf27e80..7e8335b8c3 100644 --- a/core_lib/src/interface/scribblearea.cpp +++ b/core_lib/src/interface/scribblearea.cpp @@ -475,8 +475,21 @@ void ScribbleArea::tabletEvent(QTabletEvent *e) mTabletInUse = false; } } + +#if defined Q_OS_LINUX + // Generate mouse events on linux to work around bug where the + // widget will not receive mouseEnter/mouseLeave + // events and the cursor will not update correctly. + // See https://codereview.qt-project.org/c/qt/qtbase/+/255384 + // Scribblearea should not do anything with the mouse event when mTabletInUse is true. + event.ignore(); +#else // Always accept so that mouse events are not generated (theoretically) + // Unfortunately Windows sometimes generates the events anyway + // As long as mTabletInUse is true, mouse events *should* be ignored even when + // the are generated event.accept(); +#endif } void ScribbleArea::pointerPressEvent(PointerEvent* event) @@ -504,7 +517,7 @@ void ScribbleArea::pointerPressEvent(PointerEvent* event) if (isPressed && mQuickSizing) { //qDebug() << "Start Adjusting" << event->buttons(); - if (isDoingAssistedToolAdjustment(event->modifiers())) + if (currentTool()->startAdjusting(event->modifiers(), 1)) { return; } @@ -673,30 +686,6 @@ void ScribbleArea::resizeEvent(QResizeEvent* event) updateAllFrames(); } -bool ScribbleArea::isDoingAssistedToolAdjustment(Qt::KeyboardModifiers keyMod) -{ - if ((keyMod == Qt::ShiftModifier) && (currentTool()->properties.width > -1)) - { - //adjust width if not locked - currentTool()->startAdjusting(WIDTH, 1); - return true; - } - if ((keyMod == Qt::ControlModifier) && (currentTool()->properties.feather > -1)) - { - //adjust feather if not locked - currentTool()->startAdjusting(FEATHER, 1); - return true; - } - if ((keyMod == (Qt::ControlModifier | Qt::AltModifier)) && - (currentTool()->properties.feather > -1)) - { - //adjust feather if not locked - currentTool()->startAdjusting(FEATHER, 0); - return true; - } - return false; -} - void ScribbleArea::showLayerNotVisibleWarning() { QMessageBox::warning(this, tr("Warning"), @@ -839,7 +828,7 @@ void ScribbleArea::refreshVector(const QRectF& rect, int rad) void ScribbleArea::paintCanvasCursor(QPainter& painter) { QTransform view = mEditor->view()->getView(); - QPointF mousePos = currentTool()->getCurrentPoint(); + QPointF mousePos = currentTool()->isAdjusting() ? currentTool()->getCurrentPressPoint() : currentTool()->getCurrentPoint(); int centerCal = mCursorImg.width() / 2; mTransformedCursorPos = view.map(mousePos); @@ -869,7 +858,7 @@ void ScribbleArea::updateCanvasCursor() qreal brushFeather = currentTool()->properties.feather; if (currentTool()->isAdjusting()) { - mCursorImg = currentTool()->quickSizeCursor(static_cast(brushWidth), static_cast(brushFeather), scalingFac); + mCursorImg = currentTool()->quickSizeCursor(scalingFac); } else if (mEditor->preference()->isOn(SETTING::DOTTED_CURSOR)) { @@ -1089,7 +1078,12 @@ void ScribbleArea::paintSelectionVisuals() if (selectMan->currentSelectionPolygonF().count() < 4) { return; } QPolygonF lastSelectionPolygon = editor()->view()->mapPolygonToScreen(selectMan->lastSelectionPolygonF()); - QPolygonF currentSelectionPolygon = editor()->view()->mapPolygonToScreen(selectMan->currentSelectionPolygonF()); + QPolygonF currentSelectionPolygon = selectMan->currentSelectionPolygonF(); + if (mEditor->layers()->currentLayer()->type() == Layer::BITMAP) + { + currentSelectionPolygon = currentSelectionPolygon.toPolygon(); + } + currentSelectionPolygon = editor()->view()->mapPolygonToScreen(currentSelectionPolygon); TransformParameters params = { lastSelectionPolygon, currentSelectionPolygon }; mSelectionPainter.paint(painter, object, mEditor->currentLayerIndex(), currentTool(), params); @@ -1355,7 +1349,7 @@ void ScribbleArea::applySelectionChanges() const QRectF& normalizedRect = selectMan->myTempTransformedSelectionRect().normalized(); selectMan->setTempTransformedSelectionRect(normalizedRect); } - selectMan->setSelection(selectMan->myTempTransformedSelectionRect()); + selectMan->setSelection(selectMan->myTempTransformedSelectionRect(), false); paintTransformedSelection(); // Calculate the new transformation based on the new selection @@ -1363,7 +1357,6 @@ void ScribbleArea::applySelectionChanges() // apply the transformed selection to make the selection modification absolute. applyTransformedSelection(); - } void ScribbleArea::applyTransformedSelection() @@ -1379,10 +1372,11 @@ void ScribbleArea::applyTransformedSelection() auto selectMan = mEditor->select(); if (selectMan->somethingSelected()) { - if (selectMan->mySelectionRect().isEmpty()) { return; } + if (selectMan->mySelectionRect().isEmpty() || selectMan->selectionTransform().isIdentity()) { return; } if (layer->type() == Layer::BITMAP) { + handleDrawingOnEmptyFrame(); BitmapImage* bitmapImage = currentBitmapImage(layer); BitmapImage transformedImage = bitmapImage->transformed(selectMan->mySelectionRect().toRect(), selectMan->selectionTransform(), true); @@ -1391,6 +1385,9 @@ void ScribbleArea::applyTransformedSelection() } else if (layer->type() == Layer::VECTOR) { + // Unfortunately this doesn't work right currently so vector transforms + // will always be applied on the previous keyframe when on an empty frame + //handleDrawingOnEmptyFrame(); VectorImage* vectorImage = currentVectorImage(layer); vectorImage->applySelectionTransformation(); @@ -1418,11 +1415,9 @@ void ScribbleArea::cancelTransformedSelection() vectorImage->setSelectionTransformation(QTransform()); } - mEditor->select()->setSelection(selectMan->mySelectionRect()); + mEditor->select()->setSelection(selectMan->mySelectionRect(), false); selectMan->resetSelectionProperties(); - - updateCurrentFrame(); } } @@ -1552,6 +1547,8 @@ void ScribbleArea::deleteSelection() Layer* layer = mEditor->layers()->currentLayer(); if (layer == nullptr) { return; } + handleDrawingOnEmptyFrame(); + mEditor->backup(tr("Delete Selection", "Undo Step: clear the selection area.")); selectMan->clearCurves(); diff --git a/core_lib/src/interface/scribblearea.h b/core_lib/src/interface/scribblearea.h index 81121475e0..9f0d3d0d33 100644 --- a/core_lib/src/interface/scribblearea.h +++ b/core_lib/src/interface/scribblearea.h @@ -131,8 +131,6 @@ public slots: void updateToolCursor(); void paletteColorChanged(QColor); - bool isDoingAssistedToolAdjustment(Qt::KeyboardModifiers keyMod); - void showLayerNotVisibleWarning(); diff --git a/core_lib/src/interface/timeline.cpp b/core_lib/src/interface/timeline.cpp index cb4a9608fc..7a7a0df743 100644 --- a/core_lib/src/interface/timeline.cpp +++ b/core_lib/src/interface/timeline.cpp @@ -141,6 +141,7 @@ void TimeLine::initUI() zoomSlider->setValue(mTracks->getFrameSize()); zoomSlider->setToolTip(tr("Adjust frame width")); zoomSlider->setOrientation(Qt::Horizontal); + zoomSlider->setFocusPolicy(Qt::TabFocus); timelineButtons->addWidget(keyLabel); timelineButtons->addWidget(addKeyButton); diff --git a/core_lib/src/managers/playbackmanager.cpp b/core_lib/src/managers/playbackmanager.cpp index cffbc7e5a0..44553e8fd8 100644 --- a/core_lib/src/managers/playbackmanager.cpp +++ b/core_lib/src/managers/playbackmanager.cpp @@ -86,6 +86,7 @@ Status PlaybackManager::save(Object* o) data->setMarkInFrameNumber(mMarkInFrame); data->setMarkOutFrameNumber(mMarkOutFrame); data->setFrameRate(mFps); + data->setCurrentFrame(editor()->currentFrame()); return Status::OK; } diff --git a/core_lib/src/managers/selectionmanager.cpp b/core_lib/src/managers/selectionmanager.cpp index e54c942d25..5d5dbf7087 100644 --- a/core_lib/src/managers/selectionmanager.cpp +++ b/core_lib/src/managers/selectionmanager.cpp @@ -202,6 +202,8 @@ QPointF SelectionManager::whichAnchorPoint(QPointF currentPoint) void SelectionManager::adjustSelection(const QPointF& currentPoint, qreal offsetX, qreal offsetY, qreal rotationOffset, int rotationIncrement) { + offsetX = qRound(offsetX); + offsetY = qRound(offsetY); QRectF& transformedSelection = mTransformedSelection; switch (mMoveMode) @@ -254,9 +256,13 @@ int SelectionManager::constrainRotationToAngle(const qreal& rotatedAngle, const return qRound(rotatedAngle / rotationIncrement) * rotationIncrement; } -void SelectionManager::setSelection(QRectF rect) +void SelectionManager::setSelection(QRectF rect, bool roundPixels) { resetSelectionTransformProperties(); + if (roundPixels) + { + rect = QRect(rect.topLeft().toPoint(), rect.bottomRight().toPoint() - QPoint(1,1)); + } mSelection = rect; mTransformedSelection = rect; mTempTransformedSelection = rect; @@ -371,5 +377,7 @@ void SelectionManager::resetSelectionProperties() mSomethingSelected = false; vectorSelection.clear(); + + emit selectionChanged(); } diff --git a/core_lib/src/managers/selectionmanager.h b/core_lib/src/managers/selectionmanager.h index e5d6a37453..cd01d2c790 100644 --- a/core_lib/src/managers/selectionmanager.h +++ b/core_lib/src/managers/selectionmanager.h @@ -39,7 +39,7 @@ class SelectionManager : public BaseManager void flipSelection(bool flipVertical); - void setSelection(QRectF rect); + void setSelection(QRectF rect, bool roundPixels=false); void translate(QPointF point); diff --git a/core_lib/src/movieexporter.cpp b/core_lib/src/movieexporter.cpp index 922a59df77..d131addd80 100644 --- a/core_lib/src/movieexporter.cpp +++ b/core_lib/src/movieexporter.cpp @@ -519,7 +519,8 @@ Status MovieExporter::executeFFmpeg(QString strCmd, std::function pro Status status = Status::OK; DebugDetails dd; - if (ffmpeg.waitForStarted() == true) + dd << QStringLiteral("Command: ").append(strCmd); + if (ffmpeg.waitForStarted()) { while(ffmpeg.state() == QProcess::Running) { @@ -630,6 +631,10 @@ Status MovieExporter::executeFFMpegPipe(QString strCmd, std::function(0), brushWidth) * scalingFac; - float propFeather = qMax(static_cast(0), brushFeather) * scalingFac; - float cursorWidth = propWidth + 0.5 * propFeather; + qreal propSize = qMax(0., properties.width) * scalingFac; + qreal propFeather = qMax(0., properties.feather) * scalingFac; + QRectF cursorRect(0, 0, propSize+2, propSize+2); - if (cursorWidth < 1) { cursorWidth = 1; } - float radius = cursorWidth / 2; - float xyA = 1 + propFeather / 2; - float xyB = 1 + propFeather / 8; - float whA = qMax(0, propWidth - xyA - 1); - float whB = qMax(0, cursorWidth - propFeather / 4 - 2); - QPixmap cursorPixmap = QPixmap(cursorWidth, cursorWidth); + QRectF sizeRect = cursorRect.adjusted(1, 1, -1, -1); + qreal featherRadius = (1 - propFeather / 100) * propSize / 2.; + + QPixmap cursorPixmap = QPixmap(cursorRect.size().toSize()); if (!cursorPixmap.isNull()) { cursorPixmap.fill(QColor(255, 255, 255, 0)); QPainter cursorPainter(&cursorPixmap); - cursorPainter.setPen(QColor(0, 0, 0, 255)); - cursorPainter.drawLine(QPointF(radius - 2, radius), QPointF(radius + 2, radius)); - cursorPainter.drawLine(QPointF(radius, radius - 2), QPointF(radius, radius + 2)); cursorPainter.setRenderHints(QPainter::Antialiasing, true); + + // Draw width (outside circle) + cursorPainter.setPen(QColor(255, 127, 127, 127)); + cursorPainter.setBrush(QColor(0, 255, 127, 127)); + cursorPainter.drawEllipse(sizeRect); + + // Draw feather (inside circle) + cursorPainter.setCompositionMode(QPainter::CompositionMode_Darken); cursorPainter.setPen(QColor(0, 0, 0, 0)); - cursorPainter.setBrush(QColor(0, 255, 127, 64)); - cursorPainter.setCompositionMode(QPainter::CompositionMode_Exclusion); - cursorPainter.drawEllipse(QRectF(xyB, xyB, whB, whB)); // outside circle - cursorPainter.setBrush(QColor(255, 64, 0, 255)); - cursorPainter.drawEllipse(QRectF(xyA, xyA, whA, whA)); // inside circle + cursorPainter.setBrush(QColor(0, 191, 95, 127)); + cursorPainter.drawEllipse(cursorRect.center(), featherRadius, featherRadius); + + // Draw cursor in center + cursorPainter.setRenderHints(QPainter::Antialiasing, false); + cursorPainter.setPen(QColor(0, 0, 0, 255)); + cursorPainter.drawLine(cursorRect.center() - QPoint(2, 0), cursorRect.center() + QPoint(2, 0)); + cursorPainter.drawLine(cursorRect.center() - QPoint(0, 2), cursorRect.center() + QPoint(0, 2)); + cursorPainter.end(); } return cursorPixmap; } -void BaseTool::startAdjusting(ToolPropertyType propertyType, qreal step) +bool BaseTool::startAdjusting(Qt::KeyboardModifiers modifiers, qreal step) { - msIsAdjusting = true; - mAdjustmentStep = step; - if (propertyType == WIDTH) - { - msOriginalPropertyValue = properties.width; - } - else if (propertyType == FEATHER) + if (mQuickSizingProperties.contains(modifiers)) { - msOriginalPropertyValue = properties.feather; + switch (mQuickSizingProperties.value(modifiers)) { + case WIDTH: + msOriginalPropertyValue = properties.width; + break; + case FEATHER: + msOriginalPropertyValue = properties.feather; + break; + case TOLERANCE: + msOriginalPropertyValue = properties.tolerance; + break; + default: + qDebug() << "Unhandled quick sizing property for tool" << typeName(); + Q_ASSERT(false); + return false; + } + + msIsAdjusting = true; + mAdjustmentStep = step; + mScribbleArea->updateCanvasCursor(); + return true; } - mScribbleArea->updateCanvasCursor(); + return false; } void BaseTool::stopAdjusting() @@ -304,15 +323,11 @@ void BaseTool::stopAdjusting() mEditor->getScribbleArea()->updateCanvasCursor(); } -void BaseTool::adjustCursor(Qt::KeyboardModifiers keyMod) +void BaseTool::adjustCursor(Qt::KeyboardModifiers modifiers) { - ToolPropertyType propertyType; - propertyType = (keyMod & Qt::ControlModifier) ? FEATHER : WIDTH; - + if (mQuickSizingProperties.contains(modifiers)); qreal inc = qPow(msOriginalPropertyValue * 100, 0.5); qreal newValue = inc + getCurrentPoint().x(); - int max = (propertyType == FEATHER) ? 200 : 200; - int min = (propertyType == FEATHER) ? 2 : 1; if (newValue < 0) { @@ -325,27 +340,21 @@ void BaseTool::adjustCursor(Qt::KeyboardModifiers keyMod) int tempValue = (int)(newValue / mAdjustmentStep); // + 0.5 ? newValue = tempValue * mAdjustmentStep; } - if (newValue < min) // can be optimized for size: min(200,max(0.2,newValueX)) - { - newValue = min; - } - else if (newValue > max) - { - newValue = max; - } - switch (propertyType) + switch (mQuickSizingProperties.value(modifiers)) { + case WIDTH: + mEditor->tools()->setWidth(qBound(1., newValue, 200.)); + break; case FEATHER: - if ((type() == BRUSH) || (type() == ERASER) || (this->type() == SMUDGE)) - { - mEditor->tools()->setFeather(newValue); - } + mEditor->tools()->setFeather(qBound(2., newValue, 200.)); break; - case WIDTH: - mEditor->tools()->setWidth(newValue); + case TOLERANCE: + mEditor->tools()->setTolerance(qBound(0., newValue, 100.)); break; default: + qDebug() << "Unhandled quick sizing property for tool" << typeName(); + Q_ASSERT(false); break; }; } diff --git a/core_lib/src/tool/basetool.h b/core_lib/src/tool/basetool.h index d5352db762..7b052794be 100644 --- a/core_lib/src/tool/basetool.h +++ b/core_lib/src/tool/basetool.h @@ -83,15 +83,15 @@ class BaseTool : public QObject virtual bool keyReleaseEvent(QKeyEvent*) { return false; } // dynamic cursor adjustment - virtual void startAdjusting(ToolPropertyType argSettingType, qreal argStep); + virtual bool startAdjusting(Qt::KeyboardModifiers modifiers, qreal argStep); virtual void stopAdjusting(); - virtual void adjustCursor(Qt::KeyboardModifiers keyMod); + virtual void adjustCursor(Qt::KeyboardModifiers modifiers); virtual void clearToolData() {} virtual void resetToDefault() {} static QPixmap canvasCursor(float brushWidth, float brushFeather, bool useFeather, float scalingFac, int windowWidth); - static QPixmap quickSizeCursor(float brushWidth, float brushFeather, float scalingFac); + QPixmap quickSizeCursor(qreal scalingFac); static QCursor selectMoveCursor(MoveMode mode, ToolType type); static bool isAdjusting() { return msIsAdjusting; } @@ -143,6 +143,8 @@ class BaseTool : public QObject Editor* mEditor = nullptr; ScribbleArea* mScribbleArea = nullptr; + QHash mQuickSizingProperties; + private: StrokeManager* mStrokeManager = nullptr; qreal mAdjustmentStep = 0.0f; diff --git a/core_lib/src/tool/brushtool.cpp b/core_lib/src/tool/brushtool.cpp index e3c3aa0d2e..e8bb80d1e0 100644 --- a/core_lib/src/tool/brushtool.cpp +++ b/core_lib/src/tool/brushtool.cpp @@ -66,6 +66,9 @@ void BrushTool::loadSettings() if (properties.width <= 0) { setWidth(15); } if (std::isnan(properties.feather)) { setFeather(15); } + + mQuickSizingProperties.insert(Qt::ShiftModifier, WIDTH); + mQuickSizingProperties.insert(Qt::ControlModifier, FEATHER); } void BrushTool::resetToDefault() diff --git a/core_lib/src/tool/buckettool.cpp b/core_lib/src/tool/buckettool.cpp index bde79460a6..717e873c43 100644 --- a/core_lib/src/tool/buckettool.cpp +++ b/core_lib/src/tool/buckettool.cpp @@ -147,6 +147,20 @@ void BucketTool::pointerReleaseEvent(PointerEvent* event) endStroke(); } +bool BucketTool::startAdjusting(Qt::KeyboardModifiers modifiers, qreal argStep) +{ + mQuickSizingProperties.clear(); + if (mEditor->layers()->currentLayer()->type() == Layer::VECTOR) + { + mQuickSizingProperties.insert(Qt::ShiftModifier, WIDTH); + } + else + { + mQuickSizingProperties.insert(Qt::ControlModifier, TOLERANCE); + } + return BaseTool::startAdjusting(modifiers, argStep); +} + void BucketTool::paintBitmap(Layer* layer) { Layer* targetLayer = layer; // by default diff --git a/core_lib/src/tool/buckettool.h b/core_lib/src/tool/buckettool.h index 96b2f5bc38..59aa3bcd4a 100644 --- a/core_lib/src/tool/buckettool.h +++ b/core_lib/src/tool/buckettool.h @@ -37,6 +37,8 @@ class BucketTool : public StrokeTool void pointerMoveEvent(PointerEvent*) override; void pointerReleaseEvent(PointerEvent*) override; + bool startAdjusting(Qt::KeyboardModifiers modifiers, qreal argStep) override; + void setTolerance(const int tolerance) override; void setWidth(const qreal width) override; diff --git a/core_lib/src/tool/erasertool.cpp b/core_lib/src/tool/erasertool.cpp index 270e5bd1d5..fe84ed9c36 100644 --- a/core_lib/src/tool/erasertool.cpp +++ b/core_lib/src/tool/erasertool.cpp @@ -62,6 +62,9 @@ void EraserTool::loadSettings() properties.useAA = settings.value("eraserAA", 1).toInt(); if (properties.useFeather) { properties.useAA = -1; } + + mQuickSizingProperties.insert(Qt::ShiftModifier, WIDTH); + mQuickSizingProperties.insert(Qt::ControlModifier, FEATHER); } void EraserTool::resetToDefault() @@ -187,19 +190,12 @@ void EraserTool::paintAt(QPointF point) Layer* layer = mEditor->layers()->currentLayer(); if (layer->type() == Layer::BITMAP) { - qreal opacity = 1.0; - mCurrentWidth = properties.width; - if (properties.pressure == true) - { - opacity = strokeManager()->getPressure(); - mCurrentWidth = (mCurrentWidth + (strokeManager()->getPressure() * mCurrentWidth)) * 0.5; - } - - qreal brushWidth = mCurrentWidth; - - BlitRect rect; + qreal pressure = (properties.pressure) ? mCurrentPressure : 1.0; + qreal opacity = (properties.pressure) ? (mCurrentPressure * 0.5) : 1.0; + qreal brushWidth = properties.width * pressure; + mCurrentWidth = brushWidth; - rect.extend(point.toPoint()); + BlitRect rect(point.toPoint()); mScribbleArea->drawBrush(point, brushWidth, properties.feather, @@ -208,7 +204,7 @@ void EraserTool::paintAt(QPointF point) properties.useFeather, properties.useAA == ON); - int rad = qRound(brushWidth) / 2 + 2; + int rad = qRound(brushWidth / 2 + 2); //continuously update buffer to update stroke behind grid. mScribbleArea->paintBitmapBufferRect(rect); diff --git a/core_lib/src/tool/eyedroppertool.cpp b/core_lib/src/tool/eyedroppertool.cpp index 3371879149..f8f08d7cf0 100644 --- a/core_lib/src/tool/eyedroppertool.cpp +++ b/core_lib/src/tool/eyedroppertool.cpp @@ -1,4 +1,4 @@ -/* +/* Pencil - Traditional Animation Software Copyright (C) 2005-2007 Patrick Corrieri & Pascal Naidon @@ -79,29 +79,14 @@ void EyedropperTool::pointerPressEvent(PointerEvent*) void EyedropperTool::pointerMoveEvent(PointerEvent*) { Layer* layer = mEditor->layers()->currentLayer(); - if (layer == NULL) { return; } + if (layer == nullptr) { return; } if (layer->type() == Layer::BITMAP) { - auto targetImage = static_cast(layer->getLastKeyFrameAtPosition(mEditor->currentFrame())); - if (targetImage->contains(getCurrentPoint())) + QColor pickedColor = getBitmapColor(static_cast(layer)); + if (pickedColor.isValid()) { - QColor pickedColour; - //pickedColour.setRgba(targetImage->pixel(getCurrentPoint().x(), getCurrentPoint().y())); - pickedColour.setRgba(targetImage->pixel(getCurrentPoint().x(), getCurrentPoint().y())); - int transp = 255 - pickedColour.alpha(); - pickedColour.setRed(pickedColour.red() + transp); - pickedColour.setGreen(pickedColour.green() + transp); - pickedColour.setBlue(pickedColour.blue() + transp); - - if (pickedColour.alpha() != 0) - { - mScribbleArea->setCursor(cursor(pickedColour)); - } - else - { - mScribbleArea->setCursor(cursor()); - } + mScribbleArea->setCursor(cursor(pickedColor)); } else { @@ -110,18 +95,10 @@ void EyedropperTool::pointerMoveEvent(PointerEvent*) } if (layer->type() == Layer::VECTOR) { - auto vectorImage = static_cast(layer->getLastKeyFrameAtPosition(mEditor->currentFrame())); - int colourNumber = vectorImage->getColourNumber(getCurrentPoint()); - const qreal toleranceDistance = 10.0; - QList closestCurve = vectorImage->getCurvesCloseTo(getCurrentPoint(), toleranceDistance); - if (colourNumber != -1) - { - mScribbleArea->setCursor(cursor(mEditor->object()->getColour(colourNumber).colour)); - } - else if(!closestCurve.isEmpty()) + int pickedColor = getVectorColor(static_cast(layer)); + if (pickedColor >= 0) { - int curveColor = vectorImage->getCurvesColor(closestCurve.first()); - mScribbleArea->setCursor(cursor(mEditor->object()->getColour(curveColor).colour)); + mScribbleArea->setCursor(cursor(mEditor->object()->getColour(pickedColor).colour)); } else { @@ -148,32 +125,47 @@ void EyedropperTool::updateFrontColor() if (layer == nullptr) { return; } if (layer->type() == Layer::BITMAP) { - auto targetImage = static_cast(layer->getLastKeyFrameAtPosition(mEditor->currentFrame())); - QColor pickedColour; - pickedColour.setRgba(targetImage->pixel(getLastPoint().x(), getLastPoint().y())); - int transp = 255 - pickedColour.alpha(); - pickedColour.setRed(pickedColour.red() + transp); - pickedColour.setGreen(pickedColour.green() + transp); - pickedColour.setBlue(pickedColour.blue() + transp); - if (pickedColour.alpha() != 0) + QColor pickedColor = getBitmapColor(static_cast(layer)); + if (pickedColor.isValid()) { - mEditor->color()->setColor(pickedColour); + mEditor->color()->setColor(pickedColor); } } else if (layer->type() == Layer::VECTOR) { - auto vectorImage = static_cast(layer->getLastKeyFrameAtPosition(mEditor->currentFrame())); - int colourNumber = vectorImage->getColourNumber(getLastPoint()); - const qreal toleranceDistance = 10.0; - QList closestCurve = vectorImage->getCurvesCloseTo(getCurrentPoint(), toleranceDistance); - if (colourNumber != -1) - { - mEditor->color()->setColorNumber(colourNumber); - } - else if(!closestCurve.isEmpty()) + int pickedColor = getVectorColor(static_cast(layer)); + if (pickedColor >= 0) { - int curveColor = vectorImage->getCurvesColor(closestCurve.first()); - mEditor->color()->setColorNumber(curveColor); + mEditor->color()->setColorNumber(pickedColor); } } } + +QColor EyedropperTool::getBitmapColor(LayerBitmap* layer) +{ + BitmapImage* targetImage = layer->getLastBitmapImageAtFrame(mEditor->currentFrame(), 0); + if (targetImage == nullptr || !targetImage->contains(getLastPoint())) return QColor(); + + QColor pickedColour; + pickedColour.setRgba(qUnpremultiply(targetImage->pixel(getLastPoint().x(), getLastPoint().y()))); + if (pickedColour.alpha() <= 0) pickedColour = QColor(); + return pickedColour; +} + +int EyedropperTool::getVectorColor(LayerVector* layer) +{ + VectorImage* vectorImage = layer->getLastVectorImageAtFrame(mEditor->currentFrame(), 0); + if (vectorImage == nullptr) return -1; + + // Check curves + const qreal toleranceDistance = 10.0; + QList closestCurve = vectorImage->getCurvesCloseTo(getCurrentPoint(), toleranceDistance); + if(!closestCurve.isEmpty()) + { + return vectorImage->getCurvesColor(closestCurve.last()); + } + + // Check fills + int colorNumber = vectorImage->getColourNumber(getLastPoint()); + return colorNumber; +} diff --git a/core_lib/src/tool/eyedroppertool.h b/core_lib/src/tool/eyedroppertool.h index 35109a0e63..44238f7e00 100644 --- a/core_lib/src/tool/eyedroppertool.h +++ b/core_lib/src/tool/eyedroppertool.h @@ -20,6 +20,8 @@ GNU General Public License for more details. #include "basetool.h" +class LayerBitmap; +class LayerVector; class EyedropperTool : public BaseTool { @@ -37,6 +39,12 @@ class EyedropperTool : public BaseTool /** Updates front color for bitmap and color index for vector */ void updateFrontColor(); + +private: + /** Retrieves color of the pixel under the cursor for a bitmap layer */ + QColor getBitmapColor(LayerBitmap* layer); + /** Retrieves the color index of the pixel under the cursor for a vector layer */ + int getVectorColor(LayerVector *layer); }; #endif // EYEDROPPERTOOL_H diff --git a/core_lib/src/tool/movetool.cpp b/core_lib/src/tool/movetool.cpp index 93fa190b9c..49b6ae24d1 100644 --- a/core_lib/src/tool/movetool.cpp +++ b/core_lib/src/tool/movetool.cpp @@ -250,7 +250,7 @@ void MoveTool::setCurveSelected(VectorImage* vectorImage, Qt::KeyboardModifiers applyTransformation(); } vectorImage->setSelected(selectMan->closestCurves(), true); - selectMan->setSelection(vectorImage->getSelectionRect()); + selectMan->setSelection(vectorImage->getSelectionRect(), false); } } @@ -264,7 +264,7 @@ void MoveTool::setAreaSelected(VectorImage* vectorImage, Qt::KeyboardModifiers k applyTransformation(); } vectorImage->setAreaSelected(areaNumber, true); - mEditor->select()->setSelection(vectorImage->getSelectionRect()); + mEditor->select()->setSelection(vectorImage->getSelectionRect(), false); } } diff --git a/core_lib/src/tool/penciltool.cpp b/core_lib/src/tool/penciltool.cpp index d7871a5344..dd373f995a 100644 --- a/core_lib/src/tool/penciltool.cpp +++ b/core_lib/src/tool/penciltool.cpp @@ -56,6 +56,8 @@ void PencilTool::loadSettings() properties.useFillContour = false; // properties.invisibility = 1; // properties.preserveAlpha = 0; + + mQuickSizingProperties.insert(Qt::ShiftModifier, WIDTH); } void PencilTool::resetToDefault() diff --git a/core_lib/src/tool/pentool.cpp b/core_lib/src/tool/pentool.cpp index fd861cb07f..b9967519d7 100644 --- a/core_lib/src/tool/pentool.cpp +++ b/core_lib/src/tool/pentool.cpp @@ -51,6 +51,8 @@ void PenTool::loadSettings() properties.preserveAlpha = OFF; properties.useAA = settings.value("penAA", true).toBool(); properties.stabilizerLevel = settings.value("penLineStabilization", StabilizationLevel::STRONG).toInt(); + + mQuickSizingProperties.insert(Qt::ShiftModifier, WIDTH); } void PenTool::resetToDefault() diff --git a/core_lib/src/tool/selecttool.cpp b/core_lib/src/tool/selecttool.cpp index acdfac9e0f..03cf87a319 100644 --- a/core_lib/src/tool/selecttool.cpp +++ b/core_lib/src/tool/selecttool.cpp @@ -54,8 +54,9 @@ void SelectTool::beginSelection() // paint and apply the transformation mScribbleArea->paintTransformedSelection(); mScribbleArea->applyTransformedSelection(); + mMoveMode = selectMan->validateMoveMode(getLastPoint()); - if (selectMan->somethingSelected()) // there is something selected + if (selectMan->somethingSelected() && mMoveMode != MoveMode::NONE) // there is something selected { if (mCurrentLayer->type() == Layer::VECTOR) { @@ -63,18 +64,10 @@ void SelectTool::beginSelection() } mAnchorOriginPoint = selectMan->whichAnchorPoint(getLastPoint()); - - // the user did not click on one of the corners - if (selectMan->validateMoveMode(getLastPoint()) == MoveMode::NONE) - { - const QRectF& newRect = QRectF(getLastPoint(), getLastPoint()); - selectMan->setSelection(newRect); - } } else { - selectMan->setSelection(QRectF(getCurrentPoint().x(), getCurrentPoint().y(),1,1)); - mMoveMode = MoveMode::NONE; + selectMan->setSelection(QRectF(getCurrentPoint().x(), getCurrentPoint().y(), 1, 1), mEditor->layers()->currentLayer()->type() == Layer::BITMAP); } mScribbleArea->update(); } @@ -167,17 +160,17 @@ void SelectTool::keepSelection() if (mCurrentLayer->type() == Layer::BITMAP) { if (!selectMan->myTempTransformedSelectionRect().isValid()) { - selectMan->setSelection(selectMan->myTempTransformedSelectionRect().normalized()); + selectMan->setSelection(selectMan->myTempTransformedSelectionRect().normalized(), true); } else { - selectMan->setSelection(selectMan->myTempTransformedSelectionRect()); + selectMan->setSelection(selectMan->myTempTransformedSelectionRect(), true); } } else if (mCurrentLayer->type() == Layer::VECTOR) { VectorImage* vectorImage = static_cast(mCurrentLayer)->getLastVectorImageAtFrame(mEditor->currentFrame(), 0); - selectMan->setSelection(vectorImage->getSelectionRect()); + selectMan->setSelection(vectorImage->getSelectionRect(), false); } } diff --git a/core_lib/src/tool/smudgetool.cpp b/core_lib/src/tool/smudgetool.cpp index 1b8a911ee1..0721614593 100644 --- a/core_lib/src/tool/smudgetool.cpp +++ b/core_lib/src/tool/smudgetool.cpp @@ -51,6 +51,9 @@ void SmudgeTool::loadSettings() properties.feather = settings.value("smudgeFeather", 48.0).toDouble(); properties.pressure = false; properties.stabilizerLevel = -1; + + mQuickSizingProperties.insert(Qt::ShiftModifier, WIDTH); + mQuickSizingProperties.insert(Qt::ControlModifier, FEATHER); } void SmudgeTool::resetToDefault()