diff --git a/app/app.pro b/app/app.pro index edc8d9c68f..bd10fb903d 100644 --- a/app/app.pro +++ b/app/app.pro @@ -81,6 +81,8 @@ HEADERS += \ src/importlayersdialog.h \ src/importpositiondialog.h \ src/layeropacitydialog.h \ + src/layervisibilitybutton.h \ + src/lineeditwidget.h \ src/mainwindow2.h \ src/onionskinwidget.h \ src/predefinedsetmodel.h \ @@ -91,7 +93,15 @@ HEADERS += \ src/filespage.h \ src/generalpage.h \ src/shortcutspage.h \ + src/timelinebasecell.h \ + src/timelinelayercell.h \ + src/timelinelayercelleditorwidget.h \ + src/timelinelayercellgutterwidget.h \ + src/timelinelayerheaderwidget.h \ + src/timelinelayerlist.h \ src/timelinepage.h \ + src/timelinetrackheaderwidget.h \ + src/timelinetracklist.h \ src/toolboxwidget.h \ src/toolspage.h \ src/titlebarwidget.h \ @@ -101,7 +111,6 @@ HEADERS += \ src/colorpalettewidget.h \ src/colorwheel.h \ src/timeline.h \ - src/timelinecells.h \ src/timecontrols.h \ src/cameracontextmenu.h \ src/camerapropertiesdialog.h \ @@ -135,6 +144,8 @@ SOURCES += \ src/importlayersdialog.cpp \ src/importpositiondialog.cpp \ src/layeropacitydialog.cpp \ + src/layervisibilitybutton.cpp \ + src/lineeditwidget.cpp \ src/main.cpp \ src/mainwindow2.cpp \ src/onionskinwidget.cpp \ @@ -146,7 +157,15 @@ SOURCES += \ src/filespage.cpp \ src/generalpage.cpp \ src/shortcutspage.cpp \ + src/timelinebasecell.cpp \ + src/timelinelayercell.cpp \ + src/timelinelayercelleditorwidget.cpp \ + src/timelinelayercellgutterwidget.cpp \ + src/timelinelayerheaderwidget.cpp \ + src/timelinelayerlist.cpp \ src/timelinepage.cpp \ + src/timelinetrackheaderwidget.cpp \ + src/timelinetracklist.cpp \ src/toolboxwidget.cpp \ src/toolspage.cpp \ src/titlebarwidget.cpp \ @@ -156,7 +175,6 @@ SOURCES += \ src/colorpalettewidget.cpp \ src/colorwheel.cpp \ src/timeline.cpp \ - src/timelinecells.cpp \ src/timecontrols.cpp \ src/cameracontextmenu.cpp \ src/camerapropertiesdialog.cpp \ diff --git a/app/src/actioncommands.cpp b/app/src/actioncommands.cpp index 30a42434e7..3d1e7b9f93 100644 --- a/app/src/actioncommands.cpp +++ b/app/src/actioncommands.cpp @@ -834,54 +834,25 @@ void ActionCommands::moveFrameBackward() Status ActionCommands::addNewBitmapLayer() { - bool ok; - QString text = QInputDialog::getText(nullptr, tr("Layer Properties"), - tr("Layer name:"), QLineEdit::Normal, - mEditor->layers()->nameSuggestLayer(tr("Bitmap Layer")), &ok); - if (ok && !text.isEmpty()) - { - mEditor->layers()->createBitmapLayer(text); - } + mEditor->layers()->createBitmapLayer(mEditor->layers()->nameSuggestLayer(tr("Bitmap Layer"))); return Status::OK; } Status ActionCommands::addNewVectorLayer() { - bool ok; - QString text = QInputDialog::getText(nullptr, tr("Layer Properties"), - tr("Layer name:"), QLineEdit::Normal, - mEditor->layers()->nameSuggestLayer(tr("Vector Layer")), &ok); - if (ok && !text.isEmpty()) - { - mEditor->layers()->createVectorLayer(text); - } + mEditor->layers()->createVectorLayer(mEditor->layers()->nameSuggestLayer(tr("Vector Layer"))); return Status::OK; } Status ActionCommands::addNewCameraLayer() { - bool ok; - QString text = QInputDialog::getText(nullptr, tr("Layer Properties", "A popup when creating a new layer"), - tr("Layer name:"), QLineEdit::Normal, - mEditor->layers()->nameSuggestLayer(tr("Camera Layer")), &ok); - if (ok && !text.isEmpty()) - { - mEditor->layers()->createCameraLayer(text); - } + mEditor->layers()->createCameraLayer(mEditor->layers()->nameSuggestLayer(tr("Camera Layer"))); return Status::OK; } Status ActionCommands::addNewSoundLayer() { - bool ok = false; - QString strLayerName = QInputDialog::getText(nullptr, tr("Layer Properties"), - tr("Layer name:"), QLineEdit::Normal, - mEditor->layers()->nameSuggestLayer(tr("Sound Layer")), &ok); - if (ok && !strLayerName.isEmpty()) - { - Layer* layer = mEditor->layers()->createSoundLayer(strLayerName); - mEditor->layers()->setCurrentLayer(layer); - } + mEditor->layers()->createSoundLayer(mEditor->layers()->nameSuggestLayer(tr("Sound Layer"))); return Status::OK; } diff --git a/app/src/layervisibilitybutton.cpp b/app/src/layervisibilitybutton.cpp new file mode 100644 index 0000000000..2fa6341bef --- /dev/null +++ b/app/src/layervisibilitybutton.cpp @@ -0,0 +1,149 @@ +/* +Pencil2D - Traditional Animation Software +Copyright (C) 2005-2007 Patrick Corrieri & Pascal Naidon +Copyright (C) 2008-2009 Mj Mendoza IV +Copyright (C) 2012-2020 Matthew Chiawen Chang +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; version 2 of the License. +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. +*/ +#include "layervisibilitybutton.h" + +#include "layer.h" +#include "editor.h" +#include "layermanager.h" + +#include +#include +#include +#include +#include + +LayerVisibilityButton::LayerVisibilityButton(QWidget* parent, + const LayerVisibilityContext& visibilityContext, + Layer* layer, + Editor* editor) + : QWidget(parent), + mLayer(layer), + mEditor(editor), + mVisibilityContext(visibilityContext) +{ +} + +QSize LayerVisibilityButton::sizeHint() const +{ + return QSize(22,22); +} + +void LayerVisibilityButton::paintEvent(QPaintEvent* event) +{ + QPainter painter(this); + + const QPalette& palette = QApplication::palette(); + + switch (mVisibilityContext) { + case LayerVisibilityContext::LOCAL: + paintLocalVisibilityState(painter, palette); + break; + case LayerVisibilityContext::GLOBAL: + paintGlobalVisibilityState(painter, palette); + break; + } + + painter.setRenderHint(QPainter::Antialiasing, true); + painter.drawEllipse(rect().center().x() - (mVisibilityCircleSize.width() * 0.5) + painter.pen().width(), + rect().center().y() - (mVisibilityCircleSize.height() * 0.5) + painter.pen().width(), + mVisibilityCircleSize.width(), mVisibilityCircleSize.height()); + painter.setRenderHint(QPainter::Antialiasing, false); + + mIconValid = true; +} + +void LayerVisibilityButton::paintLocalVisibilityState(QPainter& painter, const QPalette& palette) +{ + int isSelected = mLayer->id() == mEditor->layers()->currentLayer()->id(); + + const LayerVisibility& visibility = mEditor->layerVisibility(); + QColor penColor = painter.pen().color(); + + if (mEditor->layers()->currentLayer()->id() == mLayer->id()) + { + penColor = palette.color(QPalette::HighlightedText); + } + else + { + penColor = palette.color(QPalette::Text); + } + + if (!mLayer->visible()) + { + if (visibility == LayerVisibility::CURRENTONLY && !isSelected) { + penColor.setAlphaF(0.5); + } + painter.setPen(penColor); + painter.setBrush(palette.color(QPalette::Base)); + } + else + { + if ((visibility == LayerVisibility::ALL) || isSelected) + { + painter.setBrush(palette.color(QPalette::Text)); + } + else if (visibility == LayerVisibility::CURRENTONLY) + { + painter.setBrush(palette.color(QPalette::Base)); + } + else if (visibility == LayerVisibility::RELATED) + { + QColor color = palette.color(QPalette::Text); + color.setAlpha(128); + painter.setBrush(color); + } + } + + painter.setPen(penColor); +} + +void LayerVisibilityButton::paintGlobalVisibilityState(QPainter &painter, const QPalette& palette) +{ + // --- draw circle + painter.setPen(palette.color(QPalette::Text)); + if (mEditor->layerVisibility() == LayerVisibility::CURRENTONLY) + { + painter.setBrush(palette.color(QPalette::Base)); + } + else if (mEditor->layerVisibility() == LayerVisibility::RELATED) + { + QColor color = palette.color(QPalette::Text); + color.setAlpha(128); + painter.setBrush(color); + } + else if (mEditor->layerVisibility() == LayerVisibility::ALL) + { + painter.setBrush(palette.brush(QPalette::Text)); + } +} + +void LayerVisibilityButton::mousePressEvent(QMouseEvent* event) +{ + switch (mVisibilityContext) { + case LayerVisibilityContext::GLOBAL: { + if (event->buttons() & Qt::LeftButton) { + mEditor->increaseLayerVisibilityIndex(); + } else if (event->buttons() & Qt::RightButton) { + mEditor->decreaseLayerVisibilityIndex(); + } + break; + } + case LayerVisibilityContext::LOCAL: + { + mLayer->switchVisibility(); + } + } + + emit visibilityChanged(); +} diff --git a/app/src/layervisibilitybutton.h b/app/src/layervisibilitybutton.h new file mode 100644 index 0000000000..e3a399443a --- /dev/null +++ b/app/src/layervisibilitybutton.h @@ -0,0 +1,65 @@ +/* +Pencil2D - Traditional Animation Software +Copyright (C) 2005-2007 Patrick Corrieri & Pascal Naidon +Copyright (C) 2008-2009 Mj Mendoza IV +Copyright (C) 2012-2020 Matthew Chiawen Chang +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; version 2 of the License. +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. +*/ +#ifndef LAYERVISIBILITYBUTTON_H +#define LAYERVISIBILITYBUTTON_H + +#include +#include + +class Layer; +class QIcon; +class QToolButton; +class Editor; +class QPixmap; + +enum class LayerVisibilityContext +{ + LOCAL, + GLOBAL +}; + +class LayerVisibilityButton : public QWidget +{ + Q_OBJECT +public: + LayerVisibilityButton(QWidget* parent, + const LayerVisibilityContext& visibilityContext, + Layer* layer, + Editor* editor); + + void paintEvent(QPaintEvent* event) override; + + void paintLocalVisibilityState(QPainter& painter, const QPalette& palette); + void paintGlobalVisibilityState(QPainter& painter, const QPalette& palette); + + void mousePressEvent(QMouseEvent* event) override; + + QSize sizeHint() const override; + +signals: + void visibilityChanged(); + +private: + + Layer* mLayer = nullptr; + Editor* mEditor = nullptr; + QSize mVisibilityCircleSize = QSize(10,10); + + bool mIconValid = false; + + const LayerVisibilityContext mVisibilityContext; + +}; + +#endif // LAYERVISIBILITYBUTTON_H diff --git a/app/src/lineeditwidget.cpp b/app/src/lineeditwidget.cpp new file mode 100644 index 0000000000..78f93b5177 --- /dev/null +++ b/app/src/lineeditwidget.cpp @@ -0,0 +1,169 @@ +/* + +Pencil2D - Traditional Animation Software +Copyright (C) 2025 Oliver S. Larsen + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; version 2 of the License. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +*/ + +#include "lineeditwidget.h" + +#include +#include +#include + +LineEditWidget::LineEditWidget(QWidget* parent, QString text) + : QLineEdit(parent) +{ + setObjectName("LineEditWidget"); + + setStyleSheet("LineEditWidget[readOnly=true] {" + "background-color: transparent;" + "selection-color: palette(bright-text);" + "selection-background-color: palette(dark);" + "border: none;" + "}"); + setText(text); + setReadOnly(true); + + connect(this, &QLineEdit::selectionChanged, this, [=] { + + if (isReadOnly()) { + mOldText = displayText(); + } + }); +} + +void LineEditWidget::contextMenuEvent(QContextMenuEvent *event) +{ + if (isReadOnly()) { + // No context menu is shown while + // we're in read only mode. + return; + } + + QLineEdit::contextMenuEvent(event); +} + +void LineEditWidget::mousePressEvent(QMouseEvent* event) +{ + QLineEdit::mousePressEvent(event); + + const QPoint& parentPos = mapToParent(event->pos()); + if (this->geometry().contains(parentPos) && !isReadOnly()) { + event->accept(); + } else { + event->ignore(); + } +} + +void LineEditWidget::mouseMoveEvent(QMouseEvent* event) +{ + QLineEdit::mouseMoveEvent(event); + + if (isReadOnly()) { + // While we're not editing, we don't + // allow selecting the text. + setSelection(0,0); + } + // The event is ignored explicitly + // so that we can allow it to propergate up the chain + event->ignore(); +} + +void LineEditWidget::mouseReleaseEvent(QMouseEvent* event) +{ + QLineEdit::mouseReleaseEvent(event); + + // The event is ignored explicitly + // so that we can allow it to propergate up the chain + event->ignore(); +} + +void LineEditWidget::mouseDoubleClickEvent(QMouseEvent* event) +{ + QLineEdit::mouseDoubleClickEvent(event); + + setReadOnly(false); + setFocus(); + selectAll(); + + reloadStylesheet(); + + // There's no ignore event here because in this case we want to catch it. + // The event is ignored explicitly + // so that we can allow it to propergate up the chain + event->accept(); +} + +void LineEditWidget::focusOutEvent(QFocusEvent *event) +{ + QLineEdit::focusOutEvent(event); + + if (!geometry().contains(mapToParent(mapFromGlobal(QCursor::pos())))) { + // If we're clicking outside the widget, set the widget back to read only. + setReadOnly(true); + } + + reloadStylesheet(); +} + +void LineEditWidget::reloadStylesheet() +{ + // Apparently it's good enough to just call setStyleSheet with its current styling + // to make it update. + setStyleSheet(styleSheet()); +} + +void LineEditWidget::keyPressEvent(QKeyEvent* event) +{ + // We need to get the readonly value here because + // QLineEdit othewise negates our intended behavour. + bool toggle = isReadOnly(); + + QLineEdit::keyPressEvent(event); + + bool eventAccepted = false; + if (event->key() == Qt::Key_Return) { + setReadOnly(!toggle); + reloadStylesheet(); + eventAccepted = true; + } else if (event->key() == Qt::Key_Escape && !isReadOnly()) { + if (!mOldText.isEmpty()) { + setText(mOldText); + mOldText = ""; + } + setReadOnly(true); + reloadStylesheet(); + eventAccepted = true; + } + + event->setAccepted(eventAccepted); +} + +void LineEditWidget::deselect() +{ + QLineEdit::deselect(); + setReadOnly(true); + + reloadStylesheet(); +} + +void LineEditWidget::setReadOnly(bool readOnly) +{ + QLineEdit::setReadOnly(readOnly); + + if (readOnly) { + // This is silly but apparently it's + // the only way to reset the internal undo/redo history... + setText(text()); + } +} diff --git a/app/src/lineeditwidget.h b/app/src/lineeditwidget.h new file mode 100644 index 0000000000..b88b683b13 --- /dev/null +++ b/app/src/lineeditwidget.h @@ -0,0 +1,56 @@ +/* + +Pencil2D - Traditional Animation Software +Copyright (C) 2025 Oliver S. Larsen + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; version 2 of the License. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +*/ +#ifndef LINEEDITWIDGET_H +#define LINEEDITWIDGET_H + +#include +#include + +class Layer; +class QLabel; +class QLineEdit; +class QStackedLayout; + +class LineEditWidget : public QLineEdit +{ + Q_OBJECT +public: + LineEditWidget(QWidget* parent, QString text); + + void mousePressEvent(QMouseEvent*) override; + void mouseMoveEvent(QMouseEvent* event) override; + void mouseReleaseEvent(QMouseEvent* event) override; + void mouseDoubleClickEvent(QMouseEvent *) override; + void focusOutEvent(QFocusEvent* event) override; + void keyPressEvent(QKeyEvent* event) override; + void contextMenuEvent(QContextMenuEvent *event) override; + + void deselect(); + + /// Sets the widget readOnly and clears the internal undo stack. + void setReadOnly(bool readOnly); + +private: + + // The stylesheet has to be updated on every event + // where the read-only property is changed + void reloadStylesheet(); + + QString mOldText; + +}; + +#endif // LINEEDITWIDGET_H diff --git a/app/src/mainwindow2.cpp b/app/src/mainwindow2.cpp index f46df590b1..fb8d95a19e 100644 --- a/app/src/mainwindow2.cpp +++ b/app/src/mainwindow2.cpp @@ -140,6 +140,8 @@ MainWindow2::MainWindow2(QWidget* parent) : ui->background->init(mEditor->preference()); setWindowTitle(PENCIL_WINDOW_TITLE); + + setFocusPolicy(Qt::StrongFocus); } MainWindow2::~MainWindow2() diff --git a/app/src/timeline.cpp b/app/src/timeline.cpp index 92a5544e9c..b45647b353 100644 --- a/app/src/timeline.cpp +++ b/app/src/timeline.cpp @@ -28,11 +28,16 @@ GNU General Public License for more details. #include #include #include +#include +#include #include "editor.h" #include "layermanager.h" #include "timecontrols.h" -#include "timelinecells.h" +#include "timelinetracklist.h" +#include "timelinelayerlist.h" +#include "timelinelayerheaderwidget.h" +#include "timelinetrackheaderwidget.h" TimeLine::TimeLine(QWidget* parent) : BaseDockWidget(parent) @@ -47,14 +52,15 @@ void TimeLine::initUI() QWidget* timeLineContent = new QWidget(this); - mLayerList = new TimeLineCells(this, editor(), TIMELINE_CELL_TYPE::Layers); - mTracks = new TimeLineCells(this, editor(), TIMELINE_CELL_TYPE::Tracks); + mLayerHeader = new TimeLineLayerHeaderWidget(this, editor()); + mTrackHeader = new TimeLineTrackHeaderWidget(this, editor()); + mLayerList = new TimeLineLayerList(this, editor()); + mTracks = new TimeLineTrackList(this, editor()); + + mLayerHeader->setFixedHeight(mLayerList->getLayerHeight()); + mTrackHeader->setFixedHeight(mLayerList->getLayerHeight()); mHScrollbar = new QScrollBar(Qt::Horizontal); - mVScrollbar = new QScrollBar(Qt::Vertical); - mVScrollbar->setMinimum(0); - mVScrollbar->setMaximum(1); - mVScrollbar->setPageStep(1); QWidget* leftWidget = new QWidget(); leftWidget->setMinimumWidth(120); @@ -108,9 +114,26 @@ void TimeLine::initUI() addLayerButton->setMenu(layerMenu); addLayerButton->setPopupMode(QToolButton::InstantPopup); + mLayerScrollArea = new QScrollArea(); + mLayerScrollArea->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + mLayerScrollArea->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + mLayerScrollArea->setWidgetResizable(true); + mLayerScrollArea->setWidget(mLayerList); + mLayerScrollArea->setFocusPolicy(Qt::FocusPolicy::NoFocus); + mLayerScrollArea->horizontalScrollBar()->setEnabled(false); + + mTrackScrollArea = new QScrollArea(); + mTrackScrollArea->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOn); + mTrackScrollArea->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + mTrackScrollArea->setWidgetResizable(true); + mTrackScrollArea->setWidget(mTracks); + mTrackScrollArea->setFocusPolicy(Qt::FocusPolicy::NoFocus); + mTrackScrollArea->horizontalScrollBar()->setEnabled(false); + QGridLayout* leftLayout = new QGridLayout(); leftLayout->addWidget(leftToolBar, 0, 0); - leftLayout->addWidget(mLayerList, 1, 0); + leftLayout->addWidget(mLayerHeader, 1, 0); + leftLayout->addWidget(mLayerScrollArea, 2, 0); leftLayout->setContentsMargins(0, 0, 0, 0); leftLayout->setSpacing(0); leftWidget->setLayout(leftLayout); @@ -171,7 +194,8 @@ void TimeLine::initUI() QGridLayout* rightLayout = new QGridLayout(); rightLayout->addWidget(rightToolBar, 0, 0); - rightLayout->addWidget(mTracks, 1, 0); + rightLayout->addWidget(mTrackHeader, 1, 0); + rightLayout->addWidget(mTrackScrollArea, 2, 0); rightLayout->setContentsMargins(0, 0, 0, 0); rightLayout->setSpacing(0); rightWidget->setLayout(rightLayout); @@ -185,7 +209,6 @@ void TimeLine::initUI() QGridLayout* lay = new QGridLayout(); lay->addWidget(splitter, 0, 0); - lay->addWidget(mVScrollbar, 0, 1); lay->addWidget(mHScrollbar, 1, 0); lay->setContentsMargins(0, 0, 0, 0); lay->setSpacing(0); @@ -195,14 +218,21 @@ void TimeLine::initUI() mScrollingStoppedTimer = new QTimer(); mScrollingStoppedTimer->setSingleShot(true); + mLayerManager = editor()->layers(); + setWindowFlags(Qt::WindowStaysOnTopHint); + setFocusPolicy(Qt::StrongFocus); + + connect(mHScrollbar, &QScrollBar::valueChanged, mTracks, &TimeLineTrackList::hScrollChange); + connect(mHScrollbar, &QScrollBar::valueChanged, mTrackHeader, &TimeLineTrackHeaderWidget::onHScrollChange); + + connect(mTracks, &TimeLineTrackList::offsetChanged, mHScrollbar, &QScrollBar::setValue); + + connect(mLayerScrollArea->verticalScrollBar(), &QScrollBar::valueChanged, mTracks, &TimeLineTrackList::vScrollChange); + connect(mLayerScrollArea->verticalScrollBar(), &QScrollBar::valueChanged, this, &TimeLine::onScrollbarValueChanged); - connect(mHScrollbar, &QScrollBar::valueChanged, mTracks, &TimeLineCells::hScrollChange); - connect(mTracks, &TimeLineCells::offsetChanged, mHScrollbar, &QScrollBar::setValue); - connect(mVScrollbar, &QScrollBar::valueChanged, mTracks, &TimeLineCells::vScrollChange); - connect(mVScrollbar, &QScrollBar::valueChanged, mLayerList, &TimeLineCells::vScrollChange); - connect(mVScrollbar, &QScrollBar::valueChanged, this, &TimeLine::onScrollbarValueChanged); - connect(mScrollingStoppedTimer, &QTimer::timeout, mLayerList, &TimeLineCells::onScrollingVerticallyStopped); + connect(mTrackScrollArea->verticalScrollBar(), &QScrollBar::valueChanged, mTracks, &TimeLineTrackList::vScrollChange); + connect(mTrackScrollArea->verticalScrollBar(), &QScrollBar::valueChanged, this, &TimeLine::onScrollbarValueChanged); connect(splitter, &QSplitter::splitterMoved, this, &TimeLine::updateLength); @@ -210,7 +240,7 @@ void TimeLine::initUI() connect(removeKeyButton, &QToolButton::clicked, this, &TimeLine::removeKeyClick); connect(duplicateLayerButton, &QToolButton::clicked, this , &TimeLine::duplicateLayerClick); connect(duplicateKeyButton, &QToolButton::clicked, this, &TimeLine::duplicateKeyClick); - connect(zoomSlider, &QSlider::valueChanged, mTracks, &TimeLineCells::setFrameSize); + connect(zoomSlider, &QSlider::valueChanged, mTracks, &TimeLineTrackList::setFrameSize); connect(mTimeControls, &TimeControls::soundToggled, this, &TimeLine::soundClick); connect(mTimeControls, &TimeControls::fpsChanged, this, &TimeLine::fpsChanged); @@ -226,20 +256,20 @@ void TimeLine::initUI() connect(newCameraLayerAct, &QAction::triggered, this, &TimeLine::newCameraLayer); connect(mLayerDeleteButton, &QPushButton::clicked, this, &TimeLine::deleteCurrentLayerClick); - connect(mLayerList, &TimeLineCells::mouseMovedY, mLayerList, &TimeLineCells::setMouseMoveY); - connect(mLayerList, &TimeLineCells::mouseMovedY, mTracks, &TimeLineCells::setMouseMoveY); - connect(mTracks, &TimeLineCells::lengthChanged, this, &TimeLine::updateLength); - connect(mTracks, &TimeLineCells::selectionChanged, this, &TimeLine::selectionChanged); - connect(mTracks, &TimeLineCells::insertNewKeyFrame, this, &TimeLine::insertKeyClick); + connect(mLayerList, &TimeLineLayerList::cellDraggedY, mTracks, &TimeLineTrackList::setCellDragY); + connect(mTracks, &TimeLineTrackList::lengthChanged, this, &TimeLine::updateLength); + connect(mTracks, &TimeLineTrackList::selectionChanged, this, &TimeLine::selectionChanged); + connect(mTracks, &TimeLineTrackList::insertNewKeyFrame, this, &TimeLine::insertKeyClick); connect(editor(), &Editor::scrubbed, this, &TimeLine::updateFrame); connect(editor(), &Editor::frameModified, this, &TimeLine::updateContent); connect(editor(), &Editor::framesModified, this, &TimeLine::updateContent); - LayerManager* layer = editor()->layers(); - connect(layer, &LayerManager::layerCountChanged, this, &TimeLine::updateLayerNumber); - connect(layer, &LayerManager::currentLayerChanged, this, &TimeLine::onCurrentLayerChanged); - mNumLayers = layer->count(); + connect(mLayerManager, &LayerManager::layerCountChanged, this, &TimeLine::onLayerCountUpdated); + connect(mLayerManager, &LayerManager::layerOrderChanged, this, &TimeLine::onLayerOrderUpdated); + connect(mLayerManager, &LayerManager::currentLayerChanged, this, &TimeLine::onCurrentLayerChanged); + + mLayerList->loadLayerCells(); scrubbing = false; } @@ -253,6 +283,8 @@ void TimeLine::updateUICached() { mLayerList->update(); mTracks->update(); + mLayerHeader->update(); + mTrackHeader->update(); } /** Extends the timeline frame length if necessary @@ -276,7 +308,9 @@ void TimeLine::extendLength(int frame) void TimeLine::resizeEvent(QResizeEvent*) { - updateLayerView(); + if (mLayerManager) { + updateVerticalScrollbarPageCount(mLayerManager->count()); + } } void TimeLine::wheelEvent(QWheelEvent* event) @@ -285,16 +319,14 @@ void TimeLine::wheelEvent(QWheelEvent* event) { mHScrollbar->event(event); } - else - { - mVScrollbar->event(event); - } } -void TimeLine::onScrollbarValueChanged() +void TimeLine::onScrollbarValueChanged(int value) { // After the scrollbar has been updated, prepare to trigger stopped event mScrollingStoppedTimer->start(150); + mLayerScrollArea->verticalScrollBar()->setValue(value); + mTrackScrollArea->verticalScrollBar()->setValue(value); } void TimeLine::updateFrame(int frameNumber) @@ -304,23 +336,37 @@ void TimeLine::updateFrame(int frameNumber) mTracks->updateFrame(mLastUpdatedFrame); mTracks->updateFrame(frameNumber); + mTrackHeader->update(); mLastUpdatedFrame = frameNumber; } -void TimeLine::updateLayerView() +void TimeLine::updateVerticalScrollbarPageCount(int numberOfLayers) { - int pageDisplay = (mTracks->height() - mTracks->getOffsetY()) / mTracks->getLayerHeight(); + int pageDisplay = mLayerScrollArea->height() / mLayerList->getLayerHeight(); + + QScrollBar* layerScrollBar = mLayerScrollArea->verticalScrollBar(); + QScrollBar* trackScrollBar = mTrackScrollArea->verticalScrollBar(); + layerScrollBar->setRange(0, qMax(0, (numberOfLayers - pageDisplay) * mLayerList->getLayerHeight())); + layerScrollBar->setPageStep(mLayerList->getLayerHeight()); + layerScrollBar->setSingleStep(mLayerList->getLayerHeight()); + + trackScrollBar->setRange(layerScrollBar->minimum(), layerScrollBar->maximum()); + trackScrollBar->setPageStep(layerScrollBar->pageStep()); + trackScrollBar->setSingleStep(layerScrollBar->singleStep()); - mVScrollbar->setMinimum(0); - mVScrollbar->setMaximum(qMax(0, mNumLayers - pageDisplay)); updateContent(); } -void TimeLine::updateLayerNumber(int numberOfLayers) +void TimeLine::onLayerCountUpdated(int numberOfLayers) { - mNumLayers = numberOfLayers; - updateLayerView(); + mLayerList->loadLayerCells(); + updateVerticalScrollbarPageCount(numberOfLayers); +} + +void TimeLine::onLayerOrderUpdated() +{ + mLayerList->loadLayerCells(); } void TimeLine::updateLength() @@ -334,7 +380,9 @@ void TimeLine::updateLength() void TimeLine::updateContent() { mLayerList->updateContent(); + mLayerHeader->update(); mTracks->updateContent(); + mTrackHeader->update(); update(); } @@ -367,30 +415,68 @@ int TimeLine::getRangeUpper() void TimeLine::onObjectLoaded() { mTimeControls->updateUI(); - updateLayerNumber(editor()->layers()->count()); + mLayerList->loadLayerCells(); + onLayerCountUpdated(editor()->layers()->count()); } void TimeLine::onCurrentLayerChanged() { updateVerticalScrollbarPosition(); mLayerDeleteButton->setEnabled(editor()->layers()->canDeleteLayer(editor()->currentLayerIndex())); + mLayerList->updateContent(); } void TimeLine::updateVerticalScrollbarPosition() { // invert index so 0 is at the top - int idx = mNumLayers - editor()->currentLayerIndex() - 1; - // number of visible layers - int height = mNumLayers - mVScrollbar->maximum(); - // scroll bar position/offset - int pos = mVScrollbar->value(); + QScrollBar* verticalTrackScrollBar = mTrackScrollArea->verticalScrollBar(); + int layerCount = mLayerManager->count(); + int idx = layerCount - editor()->currentLayerIndex() - 1; + + float layerHeightF = static_cast(mLayerList->getLayerHeight()); + float diff = qRound(static_cast(verticalTrackScrollBar->maximum()) / layerHeightF); + int numOfVisibleCells = layerCount - diff; + + int pos = verticalTrackScrollBar->value(); + if (pos > 0) { + float fracPosition = static_cast(pos) / layerHeightF; + pos = qRound(fracPosition); + } - if (idx < pos) // above visible area - { - mVScrollbar->setValue(idx); + bool aboveVisibleArea = idx < pos; + bool belowVisibleArea = idx >= numOfVisibleCells + pos; + + if (belowVisibleArea || aboveVisibleArea) { + int value = 0; + if (aboveVisibleArea) { + value = idx; + } + else { // belowVisibleArea + value = (idx - numOfVisibleCells + 1); + } + mLayerScrollArea->verticalScrollBar()->setValue(value * mLayerList->getLayerHeight()); + mTrackScrollArea->verticalScrollBar()->setValue(value * mLayerList->getLayerHeight()); } - else if (idx >= pos + height) // below visible area +} + +void TimeLine::keyPressEvent(QKeyEvent* event) +{ + switch (event->key()) { - mVScrollbar->setValue(idx - height + 1); + case Qt::Key_Right: + editor()->scrubForward(); + break; + case Qt::Key_Left: + editor()->scrubBackward(); + break; + case Qt::Key_Up: + editor()->layers()->gotoNextLayer(); + break; + case Qt::Key_Down: + editor()->layers()->gotoPreviouslayer(); + break; + default: + break; } + event->ignore(); } diff --git a/app/src/timeline.h b/app/src/timeline.h index ec25eff0d7..218fa98114 100644 --- a/app/src/timeline.h +++ b/app/src/timeline.h @@ -21,12 +21,16 @@ GNU General Public License for more details. class QScrollBar; class Editor; -class TimeLineCells; +class TimeLineTrackList; class TimeControls; +class LayerManager; class QToolButton; class QWheelEvent; - +class QScrollArea; +class TimeLineLayerList; +class TimeLineLayerHeaderWidget; +class TimeLineTrackHeaderWidget; class TimeLine : public BaseDockWidget { @@ -40,8 +44,7 @@ class TimeLine : public BaseDockWidget void updateUICached(); void updateFrame( int frameNumber ); - void updateLayerNumber( int number ); - void updateLayerView(); + void updateVerticalScrollbarPageCount(int numberOfLayers); void updateLength(); void updateContent(); void setLoop( bool loop ); @@ -55,7 +58,9 @@ class TimeLine : public BaseDockWidget void onObjectLoaded(); void onCurrentLayerChanged(); - void onScrollbarValueChanged(); + void onScrollbarValueChanged(int value); + void onLayerCountUpdated( int number ); + void onLayerOrderUpdated(); signals: void selectionChanged(); @@ -83,6 +88,7 @@ class TimeLine : public BaseDockWidget bool scrubbing = false; protected: + void keyPressEvent(QKeyEvent* event) override; void resizeEvent( QResizeEvent* event ) override; void wheelEvent( QWheelEvent* ) override; @@ -90,15 +96,20 @@ class TimeLine : public BaseDockWidget void updateVerticalScrollbarPosition(); QScrollBar* mHScrollbar = nullptr; - QScrollBar* mVScrollbar = nullptr; - TimeLineCells* mTracks = nullptr; - TimeLineCells* mLayerList = nullptr; + TimeLineTrackList* mTracks = nullptr; + + QScrollArea* mLayerScrollArea = nullptr; + QScrollArea* mTrackScrollArea = nullptr; + TimeLineLayerHeaderWidget* mLayerHeader = nullptr; + TimeLineTrackHeaderWidget* mTrackHeader = nullptr; + TimeLineLayerList* mLayerList = nullptr; TimeControls* mTimeControls = nullptr; + LayerManager* mLayerManager = nullptr; + QTimer* mScrollingStoppedTimer = nullptr; QToolButton* mLayerDeleteButton = nullptr; - int mNumLayers = 0; int mLastUpdatedFrame = 0; }; diff --git a/app/src/timelinebasecell.cpp b/app/src/timelinebasecell.cpp new file mode 100644 index 0000000000..8b18b63c07 --- /dev/null +++ b/app/src/timelinebasecell.cpp @@ -0,0 +1,40 @@ +/* + +Pencil2D - Traditional Animation Software +Copyright (C) 2005-2007 Patrick Corrieri & Pascal Naidon +Copyright (C) 2008-2009 Mj Mendoza IV +Copyright (C) 2012-2020 Matthew Chiawen Chang + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; version 2 of the License. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +*/ + +#include "timelinebasecell.h" + +#include +#include + +#include "editor.h" +#include "layer.h" +#include "preferencemanager.h" +#include "timeline.h" + +TimeLineBaseCell::TimeLineBaseCell(TimeLine* timeline, + QWidget* parent, + Editor* editor) : QObject(parent) +{ + mTimeLine = timeline; + mEditor = editor; + mPrefs = mEditor->preference(); +} + +TimeLineBaseCell::~TimeLineBaseCell() +{ +} diff --git a/app/src/timelinebasecell.h b/app/src/timelinebasecell.h new file mode 100644 index 0000000000..ee0f532072 --- /dev/null +++ b/app/src/timelinebasecell.h @@ -0,0 +1,52 @@ +/* + +Pencil2D - Traditional Animation Software +Copyright (C) 2005-2007 Patrick Corrieri & Pascal Naidon +Copyright (C) 2008-2009 Mj Mendoza IV +Copyright (C) 2012-2020 Matthew Chiawen Chang + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; version 2 of the License. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +*/ + +#ifndef TIMELINEBASECELL_H +#define TIMELINEBASECELL_H + + +#include +#include +#include + +#include "pencildef.h" + +class TimeLine; +class Editor; +class Layer; +class PreferenceManager; +class QMouseEvent; + +class TimeLineBaseCell: public QObject { + Q_OBJECT +public: + TimeLineBaseCell(TimeLine* timeline, + QWidget* parent, + Editor* editor); + virtual ~TimeLineBaseCell(); + + virtual void setSize(const QSize& size) = 0; + + Editor* mEditor = nullptr; + TimeLine* mTimeLine = nullptr; + PreferenceManager* mPrefs = nullptr; + +private: +}; + +#endif // TIMELINEBASECELL_H diff --git a/app/src/timelinecells.cpp b/app/src/timelinecells.cpp deleted file mode 100644 index 733e3decfc..0000000000 --- a/app/src/timelinecells.cpp +++ /dev/null @@ -1,1260 +0,0 @@ -/* - -Pencil2D - Traditional Animation Software -Copyright (C) 2005-2007 Patrick Corrieri & Pascal Naidon -Copyright (C) 2012-2020 Matthew Chiawen Chang - -This program is free software; you can redistribute it and/or -modify it under the terms of the GNU General Public License -as published by the Free Software Foundation; version 2 of the License. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -*/ - -#include "timelinecells.h" - -#include -#include -#include -#include -#include -#include -#include - -#include "camerapropertiesdialog.h" -#include "editor.h" -#include "keyframe.h" -#include "layermanager.h" -#include "viewmanager.h" -#include "object.h" -#include "playbackmanager.h" -#include "preferencemanager.h" -#include "timeline.h" - -#include "cameracontextmenu.h" - -TimeLineCells::TimeLineCells(TimeLine* parent, Editor* editor, TIMELINE_CELL_TYPE type) : QWidget(parent) -{ - mTimeLine = parent; - mEditor = editor; - mPrefs = editor->preference(); - mType = type; - - mFrameLength = mPrefs->getInt(SETTING::TIMELINE_SIZE); - mFontSize = mPrefs->getInt(SETTING::LABEL_FONT_SIZE); - mFrameSize = mPrefs->getInt(SETTING::FRAME_SIZE); - mbShortScrub = mPrefs->isOn(SETTING::SHORT_SCRUB); - mDrawFrameNumber = mPrefs->isOn(SETTING::DRAW_LABEL); - - setMinimumSize(500, 4 * mLayerHeight); - setSizePolicy(QSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding)); - setAttribute(Qt::WA_OpaquePaintEvent, false); - setMouseTracking(true); - - connect(mPrefs, &PreferenceManager::optionChanged, this, &TimeLineCells::loadSetting); -} - -TimeLineCells::~TimeLineCells() -{ - delete mCache; -} - -void TimeLineCells::loadSetting(SETTING setting) -{ - switch (setting) - { - case SETTING::TIMELINE_SIZE: - mFrameLength = mPrefs->getInt(SETTING::TIMELINE_SIZE); - mTimeLine->updateLength(); - break; - case SETTING::LABEL_FONT_SIZE: - mFontSize = mPrefs->getInt(SETTING::LABEL_FONT_SIZE); - break; - case SETTING::FRAME_SIZE: - mFrameSize = mPrefs->getInt(SETTING::FRAME_SIZE); - mTimeLine->updateLength(); - break; - case SETTING::SHORT_SCRUB: - mbShortScrub = mPrefs->isOn(SETTING::SHORT_SCRUB); - break; - case SETTING::DRAW_LABEL: - mDrawFrameNumber = mPrefs->isOn(SETTING::DRAW_LABEL); - break; - default: - break; - } - - updateContent(); -} - -int TimeLineCells::getFrameNumber(int x) const -{ - return mFrameOffset + 1 + (x - mOffsetX) / mFrameSize; -} - -int TimeLineCells::getFrameX(int frameNumber) const -{ - return mOffsetX + (frameNumber - mFrameOffset) * mFrameSize; -} - -void TimeLineCells::setFrameSize(int size) -{ - mFrameSize = size; - mPrefs->set(SETTING::FRAME_SIZE, mFrameSize); - updateContent(); -} - -int TimeLineCells::getLayerNumber(int y) const -{ - int layerNumber = mLayerOffset + (y - mOffsetY) / mLayerHeight; - - int totalLayerCount = mEditor->object()->getLayerCount(); - - // Layers numbers are displayed in descending order - // The last row is layer 0 - if (layerNumber <= totalLayerCount) - layerNumber = (totalLayerCount - 1) - layerNumber; - else - layerNumber = 0; - - if (y < mOffsetY) - { - layerNumber = -1; - } - - if (layerNumber >= totalLayerCount) - { - layerNumber = totalLayerCount; - } - - //If the mouse release event if fired with mouse off the frame of the application - // mEditor->object()->getLayerCount() doesn't return the correct value. - if (layerNumber < -1) - { - layerNumber = -1; - } - return layerNumber; -} - -int TimeLineCells::getInbetweenLayerNumber(int y) const { - int layerNumber = getLayerNumber(y); - // Round the layer number towards the drag start - if(layerNumber != mFromLayer) { - if(mMouseMoveY > 0 && y < getLayerY(layerNumber) + mLayerHeight / 2) { - layerNumber++; - } - else if(mMouseMoveY < 0 && y > getLayerY(layerNumber) + mLayerHeight / 2) { - layerNumber--; - } - } - return layerNumber; -} - -int TimeLineCells::getLayerY(int layerNumber) const -{ - return mOffsetY + (mEditor->object()->getLayerCount() - 1 - layerNumber - mLayerOffset)*mLayerHeight; -} - -void TimeLineCells::updateFrame(int frameNumber) -{ - int x = getFrameX(frameNumber); - update(x - mFrameSize, 0, mFrameSize + 1, height()); -} - -void TimeLineCells::updateContent() -{ - mRedrawContent = true; - update(); -} - -bool TimeLineCells::didDetachLayer() const { - return abs(mMouseMoveY) > mLayerDetachThreshold; -} - -void TimeLineCells::showCameraMenu(QPoint pos) -{ - int frameNumber = getFrameNumber(pos.x()); - - const Layer* curLayer = mEditor->layers()->currentLayer(); - Q_ASSERT(curLayer); - - // only show menu if on camera layer and key exists - if (curLayer->type() != Layer::CAMERA || !curLayer->keyExists(frameNumber)) - { - return; - } - - mHighlightFrameEnabled = true; - mHighlightedFrame = frameNumber; - - CameraContextMenu menu(frameNumber, static_cast(curLayer)); - - menu.connect(&menu, &CameraContextMenu::aboutToHide, this, [=] { - mHighlightFrameEnabled = false; - mHighlightedFrame = -1; - update(); - - KeyFrame* key = curLayer->getKeyFrameAt(frameNumber); - if (key->isModified()) { - emit mEditor->frameModified(frameNumber); - } - }); - - // Update needs to happen before executing menu, otherwise paint event might be postponed - update(); - - menu.exec(mapToGlobal(pos)); -} - -void TimeLineCells::drawContent() -{ - if (mCache == nullptr) - { - mCache = new QPixmap(size()); - if (mCache->isNull()) - { - // fail to create cache - return; - } - } - - QPainter painter(mCache); - - // grey background of the view - const QPalette palette = QApplication::palette(); - painter.setPen(Qt::NoPen); - painter.setBrush(palette.color(QPalette::Base)); - painter.drawRect(QRect(0, 0, width(), height())); - - const int widgetWidth = width(); - - // Draw non-current layers - const Object* object = mEditor->object(); - Q_ASSERT(object != nullptr); - for (int i = 0; i < object->getLayerCount(); i++) - { - if (i == mEditor->layers()->currentLayerIndex()) - { - continue; - } - const Layer* layeri = object->getLayer(i); - - if (layeri != nullptr) - { - const int layerY = getLayerY(i); - switch (mType) - { - case TIMELINE_CELL_TYPE::Tracks: - paintTrack(painter, layeri, mOffsetX, - layerY, widgetWidth - mOffsetX, - mLayerHeight, false, mFrameSize); - break; - - case TIMELINE_CELL_TYPE::Layers: - paintLabel(painter, layeri, 0, - layerY, widgetWidth - 1, - mLayerHeight, false, mEditor->layerVisibility()); - break; - } - } - } - - // Draw current layer - const Layer* currentLayer = mEditor->layers()->currentLayer(); - if (didDetachLayer()) - { - int layerYMouseMove = getLayerY(mEditor->layers()->currentLayerIndex()) + mMouseMoveY; - if (mType == TIMELINE_CELL_TYPE::Tracks) - { - paintTrack(painter, currentLayer, - mOffsetX, layerYMouseMove, - widgetWidth - mOffsetX, mLayerHeight, - true, mFrameSize); - } - else if (mType == TIMELINE_CELL_TYPE::Layers) - { - paintLabel(painter, currentLayer, - 0, layerYMouseMove, - widgetWidth - 1, mLayerHeight, true, mEditor->layerVisibility()); - - paintLayerGutter(painter); - } - } - else - { - if (mType == TIMELINE_CELL_TYPE::Tracks) - { - paintTrack(painter, - currentLayer, - mOffsetX, - getLayerY(mEditor->layers()->currentLayerIndex()), - widgetWidth - mOffsetX, - mLayerHeight, - true, - mFrameSize); - } - else if (mType == TIMELINE_CELL_TYPE::Layers) - { - paintLabel(painter, - currentLayer, - 0, - getLayerY(mEditor->layers()->currentLayerIndex()), - widgetWidth - 1, - mLayerHeight, - true, - mEditor->layerVisibility()); - } - } - - // --- draw track bar background - painter.setPen(Qt::NoPen); - painter.setBrush(palette.color(QPalette::Base)); - painter.drawRect(QRect(0, 0, width() - 1, mOffsetY - 1)); - - // --- draw bottom line splitter for track bar - painter.setPen(palette.color(QPalette::Mid)); - painter.drawLine(0, mOffsetY - 2, width() - 1, mOffsetY - 2); - - if (mType == TIMELINE_CELL_TYPE::Layers) - { - // --- draw circle - painter.setPen(palette.color(QPalette::Text)); - if (mEditor->layerVisibility() == LayerVisibility::CURRENTONLY) - { - painter.setBrush(palette.color(QPalette::Base)); - } - else if (mEditor->layerVisibility() == LayerVisibility::RELATED) - { - QColor color = palette.color(QPalette::Text); - color.setAlpha(128); - painter.setBrush(color); - } - else if (mEditor->layerVisibility() == LayerVisibility::ALL) - { - painter.setBrush(palette.brush(QPalette::Text)); - } - painter.setRenderHint(QPainter::Antialiasing, true); - painter.drawEllipse(6, 4, 9, 9); - painter.setRenderHint(QPainter::Antialiasing, false); - } - else if (mType == TIMELINE_CELL_TYPE::Tracks) - { - paintTicks(painter, palette); - - for (int i = 0; i < object->getLayerCount(); i++) { - paintSelectedFrames(painter, object->getLayer(i), i); - } - } - mRedrawContent = false; -} - -void TimeLineCells::paintTicks(QPainter& painter, const QPalette& palette) const -{ - painter.setPen(palette.color(QPalette::Text)); - painter.setBrush(palette.brush(QPalette::Text)); - int fps = mEditor->playback()->fps(); - for (int i = mFrameOffset; i < mFrameOffset + (width() - mOffsetX) / mFrameSize; i++) - { - // line x pos + some offset - const int lineX = getFrameX(i) + 1; - if (i + 1 >= mTimeLine->getRangeLower() && i < mTimeLine->getRangeUpper()) - { - painter.setPen(Qt::NoPen); - painter.setBrush(palette.color(QPalette::Highlight)); - - painter.drawRect(lineX, 1, mFrameSize + 1, 2); - - painter.setPen(palette.color(QPalette::Text)); - painter.setBrush(palette.brush(QPalette::Text)); - } - - // Draw large tick at fps mark - if (i % fps == 0 || i % fps == fps / 2) - { - painter.drawLine(lineX, 1, lineX, 5); - } - else // draw small tick - { - painter.drawLine(lineX, 1, lineX, 3); - } - if (i == 0 || i % fps == fps - 1) - { - int incr = (i < 9) ? 4 : 0; // poor man’s text centering - painter.drawText(QPoint(lineX + incr, 15), QString::number(i + 1)); - } - } -} - -void TimeLineCells::paintTrack(QPainter& painter, const Layer* layer, - int x, int y, int width, int height, - bool selected, int frameSize) const -{ - const QPalette palette = QApplication::palette(); - QColor col; - // Color each track according to the layer type - if (layer->type() == Layer::BITMAP) col = QColor(51, 155, 252); - if (layer->type() == Layer::VECTOR) col = QColor(70, 205, 123); - if (layer->type() == Layer::SOUND) col = QColor(255, 141, 112); - if (layer->type() == Layer::CAMERA) col = QColor(253, 202, 92); - // Dim invisible layers - if (!layer->visible()) col.setAlpha(64); - - painter.save(); - painter.setBrush(col); - painter.setPen(QPen(QBrush(palette.color(QPalette::Mid)), 1, Qt::SolidLine, Qt::RoundCap, Qt::RoundJoin)); - painter.drawRect(x, y - 1, width, height); - - if (!layer->visible()) - { - painter.restore(); - return; - } - - // Changes the appearance if selected - if (selected) - { - paintSelection(painter, x, y, width, height); - } - else - { - painter.save(); - QLinearGradient linearGrad(QPointF(0, y), QPointF(0, y + height)); - linearGrad.setColorAt(0, QColor(255,255,255,150)); - linearGrad.setColorAt(1, QColor(0,0,0,0)); - painter.setCompositionMode(QPainter::CompositionMode_Overlay); - painter.setBrush(linearGrad); - painter.drawRect(x, y - 1, width, height); - painter.restore(); - } - - paintFrames(painter, col, layer, y, height, selected, frameSize); - - painter.restore(); -} - -void TimeLineCells::paintFrames(QPainter& painter, QColor trackCol, const Layer* layer, int y, int height, bool selected, int frameSize) const -{ - painter.setPen(QPen(QBrush(QColor(40, 40, 40)), 1, Qt::SolidLine, Qt::RoundCap, Qt::RoundJoin)); - - int recTop = y + 1; - int standardWidth = frameSize - 2; - - int recHeight = height - 4; - - const QList selectedFrames = layer->getSelectedFramesByPos(); - layer->foreachKeyFrame([&](KeyFrame* key) - { - int framePos = key->pos(); - int recWidth = standardWidth; - int recLeft = getFrameX(framePos) - recWidth; - - // Selected frames are painted separately - if (selectedFrames.contains(framePos)) { - return; - } - - if (key->length() > 1) - { - // This is especially for sound clips. - // Sound clips are the only type of KeyFrame with variable frame length. - recWidth = frameSize * key->length(); - } - - // Paint the frame border - painter.setPen(QPen(QBrush(QColor(40, 40, 40)), 1, Qt::SolidLine, Qt::RoundCap, Qt::RoundJoin)); - - // Paint the frame contents - if (selected) - { - painter.setBrush(QColor(trackCol.red(), trackCol.green(), trackCol.blue(), 150)); - } - - painter.drawRect(recLeft, recTop, recWidth, recHeight); - }); -} - -void TimeLineCells::paintCurrentFrameBorder(QPainter &painter, int recLeft, int recTop, int recWidth, int recHeight) const -{ - painter.save(); - painter.setBrush(Qt::NoBrush); - painter.setPen(Qt::white); - painter.drawRect(recLeft, recTop, recWidth, recHeight); - painter.restore(); -} - -void TimeLineCells::paintFrameCursorOnCurrentLayer(QPainter &painter, int recTop, int recWidth, int recHeight) const -{ - int recLeft = getFrameX(mFramePosMoveX) - recWidth; - - painter.save(); - const QPalette palette = QApplication::palette(); - // Don't fill - painter.setBrush(Qt::NoBrush); - // paint border - QColor penColor = palette.color(QPalette::WindowText); - penColor.setAlpha(127); - painter.setPen(penColor); - painter.drawRect(recLeft, recTop, recWidth, recHeight); - painter.restore(); -} - -void TimeLineCells::paintHighlightedFrame(QPainter& painter, int framePos, int recTop, int recWidth, int recHeight) const -{ - int recLeft = getFrameX(framePos) - recWidth; - - painter.save(); - const QPalette palette = QApplication::palette(); - painter.setBrush(palette.color(QPalette::Window)); - painter.setPen(palette.color(QPalette::WindowText)); - - // Draw a rect slighly smaller than the frame - painter.drawRect(recLeft+1, recTop+1, recWidth-1, recHeight-1); - painter.restore(); -} - -void TimeLineCells::paintSelectedFrames(QPainter& painter, const Layer* layer, const int layerIndex) const -{ - int mouseX = mMouseMoveX; - int posUnderCursor = getFrameNumber(mMousePressX); - int standardWidth = mFrameSize - 2; - int recWidth = standardWidth; - int recHeight = mLayerHeight - 4; - int recTop = getLayerY(layerIndex) + 1; - - painter.save(); - for (int framePos : layer->getSelectedFramesByPos()) { - - KeyFrame* key = layer->getKeyFrameAt(framePos); - if (key->length() > 1) - { - // This is a special case for sound clip. - // Sound clip is the only type of KeyFrame that has variable frame length. - recWidth = mFrameSize * key->length(); - } - - painter.setBrush(QColor(60, 60, 60)); - painter.setPen(QPen(QBrush(QColor(40, 40, 40)), 1, Qt::SolidLine, Qt::RoundCap, Qt::RoundJoin)); - - int frameX = getFrameX(framePos); - if (mMovingFrames) { - int offset = (framePos - posUnderCursor) + mFrameOffset; - int newFrameX = getFrameX(getFrameNumber(getFrameX(offset)+mouseX)) - standardWidth; - // Paint as frames are hovering - painter.drawRect(newFrameX, recTop-4, recWidth, recHeight); - - } else { - int currentFrameX = frameX - standardWidth; - painter.drawRect(currentFrameX, recTop, recWidth, recHeight); - } - } - painter.restore(); -} - -void TimeLineCells::paintLabel(QPainter& painter, const Layer* layer, - int x, int y, int width, int height, - bool selected, LayerVisibility layerVisibility) const -{ - const QPalette palette = QApplication::palette(); - - if (selected) - { - painter.setBrush(palette.color(QPalette::Highlight)); - } - else - { - painter.setBrush(palette.color(QPalette::Base)); - } - painter.setPen(Qt::NoPen); - painter.drawRect(x, y - 1, width, height); // empty rectangle by default - - if (!layer->visible()) - { - painter.setBrush(palette.color(QPalette::Base)); - } - else - { - if ((layerVisibility == LayerVisibility::ALL) || selected) - { - painter.setBrush(palette.color(QPalette::Text)); - } - else if (layerVisibility == LayerVisibility::CURRENTONLY) - { - painter.setBrush(palette.color(QPalette::Base)); - } - else if (layerVisibility == LayerVisibility::RELATED) - { - QColor color = palette.color(QPalette::Text); - color.setAlpha(128); - painter.setBrush(color); - } - } - if (selected) - { - painter.setPen(palette.color(QPalette::HighlightedText)); - } - else - { - painter.setPen(palette.color(QPalette::Text)); - } - painter.setRenderHint(QPainter::Antialiasing, true); - painter.drawEllipse(x + 6, y + 4, 9, 9); - painter.setRenderHint(QPainter::Antialiasing, false); - - if (layer->type() == Layer::BITMAP) painter.drawPixmap(QPoint(22, y - 1), QPixmap(":icons/themes/playful/timeline/cell-bitmap.svg")); - if (layer->type() == Layer::VECTOR) painter.drawPixmap(QPoint(22, y - 1), QPixmap(":icons/themes/playful/timeline/cell-vector.svg")); - if (layer->type() == Layer::SOUND) painter.drawPixmap(QPoint(22, y - 1), QPixmap(":icons/themes/playful/timeline/cell-sound.svg")); - if (layer->type() == Layer::CAMERA) painter.drawPixmap(QPoint(22, y - 1), QPixmap(":icons/themes/playful/timeline/cell-camera.svg")); - - if (selected) - { - painter.setPen(palette.color(QPalette::HighlightedText)); - } - else - { - painter.setPen(palette.color(QPalette::Text)); - } - painter.drawText(QPoint(45, y + (2 * height) / 3), layer->name()); -} - -void TimeLineCells::paintSelection(QPainter& painter, int x, int y, int width, int height) const -{ - QLinearGradient linearGrad(QPointF(0, y), QPointF(0, y + height)); - linearGrad.setColorAt(0, QColor(0, 0, 0, 255)); - linearGrad.setColorAt(1, QColor(255, 255, 255, 0)); - painter.save(); - painter.setCompositionMode(QPainter::CompositionMode_Overlay); - painter.setBrush(linearGrad); - painter.setPen(Qt::NoPen); - painter.drawRect(x, y, width, height - 1); - painter.restore(); -} - -void TimeLineCells::paintLayerGutter(QPainter& painter) const -{ - painter.setPen(QApplication::palette().color(QPalette::Mid)); - if (mMouseMoveY > mLayerDetachThreshold) - { - painter.drawRect(0, getLayerY(getInbetweenLayerNumber(mEndY))+mLayerHeight, width(), 2); - } - else - { - painter.drawRect(0, getLayerY(getInbetweenLayerNumber(mEndY)), width(), 2); - } -} - -void TimeLineCells::paintOnionSkin(QPainter& painter) const -{ - Layer* layer = mEditor->layers()->currentLayer(); - if (layer == nullptr) { return; } - - int frameNumber = mEditor->currentFrame(); - - int prevOnionSkinCount = mEditor->preference()->getInt(SETTING::ONION_PREV_FRAMES_NUM); - int nextOnionSkinCount = mEditor->preference()->getInt(SETTING::ONION_NEXT_FRAMES_NUM); - - bool isAbsolute = (mEditor->preference()->getString(SETTING::ONION_TYPE) == "absolute"); - - if (mEditor->preference()->isOn(SETTING::PREV_ONION) && prevOnionSkinCount > 0) - { - int onionFrameNumber = frameNumber; - if (isAbsolute) - { - onionFrameNumber = layer->getPreviousFrameNumber(onionFrameNumber+1, true); - } - onionFrameNumber = layer->getPreviousFrameNumber(onionFrameNumber, isAbsolute); - int onionPosition = 0; - - while (onionPosition < prevOnionSkinCount && onionFrameNumber > 0) - { - painter.setBrush(QColor(128, 128, 128, 128)); - painter.setPen(Qt::NoPen); - QRect onionRect; - onionRect.setTopLeft(QPoint(getFrameX(onionFrameNumber - 1), 0)); - onionRect.setBottomRight(QPoint(getFrameX(onionFrameNumber), height())); - onionRect.setBottomRight(QPoint(getFrameX(onionFrameNumber), 19)); - painter.drawRect(onionRect); - - onionFrameNumber = layer->getPreviousFrameNumber(onionFrameNumber, isAbsolute); - onionPosition++; - } - } - - if (mEditor->preference()->isOn(SETTING::NEXT_ONION) && nextOnionSkinCount > 0) { - - int onionFrameNumber = layer->getNextFrameNumber(frameNumber, isAbsolute); - int onionPosition = 0; - - while (onionPosition < nextOnionSkinCount && onionFrameNumber > 0) - { - painter.setBrush(QColor(128, 128, 128, 128)); - painter.setPen(Qt::NoPen); - QRect onionRect; - onionRect.setTopLeft(QPoint(getFrameX(onionFrameNumber - 1), 0)); - onionRect.setBottomRight(QPoint(getFrameX(onionFrameNumber), height())); - onionRect.setBottomRight(QPoint(getFrameX(onionFrameNumber), 19)); - painter.drawRect(onionRect); - - onionFrameNumber = layer->getNextFrameNumber(onionFrameNumber, isAbsolute); - onionPosition++; - } - } -} - -void TimeLineCells::paintEvent(QPaintEvent*) -{ - const QPalette palette = QApplication::palette(); - QPainter painter(this); - - bool isPlaying = mEditor->playback()->isPlaying(); - if (mCache == nullptr || mRedrawContent || trackScrubber()) - { - drawContent(); - } - if (mCache) - { - painter.drawPixmap(QPoint(0, 0), *mCache); - } - - if (mType == TIMELINE_CELL_TYPE::Tracks) - { - if (!isPlaying) { - paintOnionSkin(painter); - } - - int currentFrame = mEditor->currentFrame(); - Layer* currentLayer = mEditor->layers()->currentLayer(); - KeyFrame* keyFrame = currentLayer->getKeyFrameWhichCovers(currentFrame); - if (keyFrame != nullptr) - { - int recWidth = keyFrame->length() == 1 ? mFrameSize - 2 : mFrameSize * keyFrame->length(); - int recLeft = getFrameX(keyFrame->pos()) - (mFrameSize - 2); - paintCurrentFrameBorder(painter, recLeft, getLayerY(mEditor->currentLayerIndex()) + 1, recWidth, mLayerHeight - 4); - } - - if (!mMovingFrames && mLayerPosMoveY != -1 && mLayerPosMoveY == mEditor->currentLayerIndex()) - { - // This is terrible but well... - int recTop = getLayerY(mLayerPosMoveY) + 1; - int standardWidth = mFrameSize - 2; - int recHeight = mLayerHeight - 4; - - if (mHighlightFrameEnabled) - { - paintHighlightedFrame(painter, mHighlightedFrame, recTop, standardWidth, recHeight); - } - if (currentLayer->visible()) - { - paintFrameCursorOnCurrentLayer(painter, recTop, standardWidth, recHeight); - } - } - - // --- draw the position of the current frame - if (currentFrame > mFrameOffset) - { - QColor scrubColor = palette.color(QPalette::Highlight); - scrubColor.setAlpha(160); - painter.setBrush(scrubColor); - painter.setPen(Qt::NoPen); - - int currentFrameStartX = getFrameX(currentFrame - 1) + 1; - int currentFrameEndX = getFrameX(currentFrame); - QRect scrubRect; - scrubRect.setTopLeft(QPoint(currentFrameStartX, 0)); - scrubRect.setBottomRight(QPoint(currentFrameEndX, height())); - if (mbShortScrub) - { - scrubRect.setBottomRight(QPoint(currentFrameEndX, 19)); - } - painter.save(); - - bool mouseUnderScrubber = currentFrame == mFramePosMoveX; - if (mouseUnderScrubber) { - QRect smallScrub = QRect(QPoint(currentFrameStartX, 0), QPoint(currentFrameEndX,19)); - QPen pen = scrubColor; - pen.setWidth(2); - painter.setPen(pen); - painter.drawRect(smallScrub); - painter.setBrush(Qt::NoBrush); - } - painter.drawRect(scrubRect); - painter.restore(); - - painter.setPen(palette.color(QPalette::HighlightedText)); - int incr = (currentFrame < 10) ? 4 : 0; - painter.drawText(QPoint(currentFrameStartX + incr, 15), - QString::number(currentFrame)); - } - } -} - -void TimeLineCells::resizeEvent(QResizeEvent* event) -{ - clearCache(); - updateContent(); - event->accept(); - emit lengthChanged(getFrameLength()); -} - -bool TimeLineCells::event(QEvent* event) -{ - if (event->type() == QEvent::Leave) { - onDidLeaveWidget(); - } - - return QWidget::event(event); -} - -void TimeLineCells::mousePressEvent(QMouseEvent* event) -{ - int frameNumber = getFrameNumber(event->pos().x()); - int layerNumber = getLayerNumber(event->pos().y()); - mCurrentLayerNumber = layerNumber; - - mMousePressX = event->pos().x(); - mFromLayer = mToLayer = layerNumber; - - mStartY = event->pos().y(); - mStartLayerNumber = layerNumber; - mEndY = event->pos().y(); - - mStartFrameNumber = frameNumber; - mLastFrameNumber = mStartFrameNumber; - - mCanMoveFrame = false; - mMovingFrames = false; - - mCanBoxSelect = false; - mBoxSelecting = false; - - mClickSelecting = false; - - primaryButton = event->button(); - - switch (mType) - { - case TIMELINE_CELL_TYPE::Layers: - if (layerNumber != -1 && layerNumber < mEditor->object()->getLayerCount()) - { - if (event->pos().x() < 15) - { - mEditor->switchVisibilityOfLayer(layerNumber); - } - else if (mEditor->currentLayerIndex() != layerNumber) - { - mEditor->layers()->setCurrentLayer(layerNumber); - mEditor->layers()->currentLayer()->deselectAll(); - } - } - if (layerNumber == -1) - { - if (event->pos().x() < 15) - { - if (event->button() == Qt::LeftButton) { - mEditor->increaseLayerVisibilityIndex(); - } else if (event->button() == Qt::RightButton) { - mEditor->decreaseLayerVisibilityIndex(); - } - } - } - break; - case TIMELINE_CELL_TYPE::Tracks: - if (event->button() == Qt::MiddleButton) - { - mLastFrameNumber = getFrameNumber(event->pos().x()); - } - else - { - if (frameNumber == mEditor->currentFrame() && mStartY < 20) - { - if (mEditor->playback()->isPlaying()) - { - mEditor->playback()->stop(); - } - mTimeLine->scrubbing = true; - } - else - { - if ((layerNumber != -1) && layerNumber < mEditor->object()->getLayerCount()) - { - int previousLayerNumber = mEditor->layers()->currentLayerIndex(); - - if (previousLayerNumber != layerNumber) - { - Layer *previousLayer = mEditor->object()->getLayer(previousLayerNumber); - previousLayer->deselectAll(); - emit mEditor->selectedFramesChanged(); - mEditor->layers()->setCurrentLayer(layerNumber); - } - - Layer *currentLayer = mEditor->object()->getLayer(layerNumber); - - // Check if we are using the alt key - if (event->modifiers() == Qt::AltModifier) - { - // If it is the case, we select everything that is after the selected frame - mClickSelecting = true; - mCanMoveFrame = true; - - currentLayer->selectAllFramesAfter(frameNumber); - emit mEditor->selectedFramesChanged(); - } - // Check if we are clicking on a non selected frame - else if (!currentLayer->isFrameSelected(frameNumber)) - { - // If it is the case, we select it if it is the left button... - mCanBoxSelect = true; - mClickSelecting = true; - if (event->button() == Qt::LeftButton) - { - - if (event->modifiers() == Qt::ControlModifier) - { - // Add/remove from already selected - currentLayer->toggleFrameSelected(frameNumber, true); - emit mEditor->selectedFramesChanged(); - } - else if (event->modifiers() == Qt::ShiftModifier) - { - // Select a range from the last selected - currentLayer->extendSelectionTo(frameNumber); - emit mEditor->selectedFramesChanged(); - } - else - { - // Only select if left button clicked - currentLayer->toggleFrameSelected(frameNumber, false); - emit mEditor->selectedFramesChanged(); - } - } - - // ... or we show the camera context menu, if it is the right button - if (event->button() == Qt::RightButton) - { - showCameraMenu(event->pos()); - } - - } - else - { - // If selected they can also be interpolated - if (event->button() == Qt::RightButton) - { - showCameraMenu(event->pos()); - } - // We clicked on a selected frame, we can move it - mCanMoveFrame = true; - } - - if (currentLayer->hasAnySelectedFrames()) { - emit selectionChanged(); - } - - mTimeLine->updateContent(); - } - else - { - if (frameNumber > 0) - { - if (mEditor->playback()->isPlaying()) - { - mEditor->playback()->stop(); - } - if (mEditor->playback()->getSoundScrubActive() && mLastScrubFrame != frameNumber) - { - mEditor->playback()->playScrub(frameNumber); - mLastScrubFrame = frameNumber; - } - - mEditor->scrubTo(frameNumber); - - mTimeLine->scrubbing = true; - qDebug("Scrub to %d frame", frameNumber); - } - } - } - } - break; - } -} - -void TimeLineCells::mouseMoveEvent(QMouseEvent* event) -{ - mMouseMoveX = event->pos().x(); - mFramePosMoveX = getFrameNumber(mMouseMoveX); - mLayerPosMoveY = getLayerNumber(event->pos().y()); - - if (mType == TIMELINE_CELL_TYPE::Layers) - { - if (event->buttons() & Qt::LeftButton ) { - mEndY = event->pos().y(); - emit mouseMovedY(mEndY - mStartY); - } - } - else if (mType == TIMELINE_CELL_TYPE::Tracks) - { - if (primaryButton == Qt::MiddleButton) - { - mFrameOffset = qMin(qMax(0, mFrameLength - width() / getFrameSize()), qMax(0, mFrameOffset + mLastFrameNumber - mFramePosMoveX)); - update(); - emit offsetChanged(mFrameOffset); - } - else - { - if (mTimeLine->scrubbing) - { - if (mEditor->playback()->getSoundScrubActive() && mLastScrubFrame != mFramePosMoveX) - { - mEditor->playback()->playScrub(mFramePosMoveX); - mLastScrubFrame = mFramePosMoveX; - } - mEditor->scrubTo(mFramePosMoveX); - } - else - { - if (event->buttons() & Qt::LeftButton) { - if (mStartLayerNumber != -1 && mStartLayerNumber < mEditor->object()->getLayerCount()) - { - Layer *currentLayer = mEditor->object()->getLayer(mStartLayerNumber); - - // Check if the frame we clicked was selected - if (mCanMoveFrame) { - - // If it is the case, we move the selected frames in the layer - mMovingFrames = true; - } - else if (mCanBoxSelect) - { - // Otherwise, we do a box select - mBoxSelecting = true; - - currentLayer->deselectAll(); - currentLayer->setFrameSelected(mStartFrameNumber, true); - currentLayer->extendSelectionTo(mFramePosMoveX); - emit mEditor->selectedFramesChanged(); - } - mLastFrameNumber = mFramePosMoveX; - updateContent(); - } - } - update(); - } - } - } -} - -void TimeLineCells::mouseReleaseEvent(QMouseEvent* event) -{ - if (event->button() != primaryButton) return; - - int frameNumber = getFrameNumber(event->pos().x()); - if (frameNumber < 1) frameNumber = 1; - int layerNumber = getLayerNumber(event->pos().y()); - - if (mType == TIMELINE_CELL_TYPE::Tracks && mCurrentLayerNumber != -1 && primaryButton != Qt::MiddleButton) - { - // We should affect the current layer based on what's selected, not where the mouse currently is. - Layer* currentLayer = mEditor->layers()->getLayer(mCurrentLayerNumber); - Q_ASSERT(currentLayer); - - if (mMovingFrames) - { - int posUnderCursor = getFrameNumber(mMousePressX); - int offset = frameNumber - posUnderCursor; - - currentLayer->moveSelectedFrames(offset); - - mEditor->layers()->notifyAnimationLengthChanged(); - emit mEditor->framesModified(); - updateContent(); - } - else if (!mTimeLine->scrubbing && !mMovingFrames && !mClickSelecting && !mBoxSelecting) - { - // De-selecting if we didn't move, scrub nor select anything - bool multipleSelection = (event->modifiers() == Qt::ControlModifier); - - // Add/remove from already selected - currentLayer->toggleFrameSelected(frameNumber, multipleSelection); - emit mEditor->selectedFramesChanged(); - updateContent(); - } - } - if (mType == TIMELINE_CELL_TYPE::Layers && !mScrollingVertically && layerNumber != mStartLayerNumber && mStartLayerNumber != -1 && layerNumber != -1) - { - mToLayer = getInbetweenLayerNumber(event->pos().y()); - if (mToLayer != mFromLayer && mToLayer > -1 && mToLayer < mEditor->layers()->count()) - { - // Bubble the from layer up or down to the to layer - if (mToLayer < mFromLayer) // bubble up - { - for (int i = mFromLayer - 1; i >= mToLayer; i--) - mEditor->swapLayers(i, i + 1); - } - else // bubble down - { - for (int i = mFromLayer + 1; i <= mToLayer; i++) - mEditor->swapLayers(i, i - 1); - } - } - } - - if (mType == TIMELINE_CELL_TYPE::Layers && event->button() == Qt::LeftButton) - { - emit mouseMovedY(0); - } - - primaryButton = Qt::NoButton; - mEndY = mStartY; - mTimeLine->scrubbing = false; - mMovingFrames = false; -} - -void TimeLineCells::mouseDoubleClickEvent(QMouseEvent* event) -{ - int frameNumber = getFrameNumber(event->pos().x()); - int layerNumber = getLayerNumber(event->pos().y()); - - // -- short scrub -- - if (event->pos().y() < 20 && (mType != TIMELINE_CELL_TYPE::Layers || event->pos().x() >= 15)) - { - mPrefs->set(SETTING::SHORT_SCRUB, !mbShortScrub); - } - - // -- layer -- - Layer* layer = mEditor->object()->getLayer(layerNumber); - if (layer && event->buttons() & Qt::LeftButton) - { - if (mType == TIMELINE_CELL_TYPE::Tracks && (layerNumber != -1) && (frameNumber > 0) && layerNumber < mEditor->object()->getLayerCount()) - { - if (!layer->keyExistsWhichCovers(frameNumber)) - { - mEditor->scrubTo(frameNumber); - } - - // The release event will toggle the frame on again, so we make sure it gets - // deselected now instead. - layer->setFrameSelected(frameNumber, true); - } - else if (mType == TIMELINE_CELL_TYPE::Layers && event->pos().x() >= 15) - { - editLayerProperties(layer); - } - } - QWidget::mouseDoubleClickEvent(event); -} - -void TimeLineCells::editLayerProperties(Layer *layer) const -{ - if (layer->type() != Layer::CAMERA) - { - editLayerName(layer); - return; - } - - auto cameraLayer = dynamic_cast(layer); - Q_ASSERT(cameraLayer); - editLayerProperties(cameraLayer); -} - -void TimeLineCells::editLayerProperties(LayerCamera* cameraLayer) const -{ - QRegularExpression regex("([\\x{FFEF}-\\x{FFFF}])+"); - - CameraPropertiesDialog dialog(cameraLayer->name(), cameraLayer->getViewRect().width(), - cameraLayer->getViewRect().height()); - if (dialog.exec() != QDialog::Accepted) - { - return; - } - QString name = dialog.getName().replace(regex, ""); - - if (!name.isEmpty()) - { - mEditor->layers()->renameLayer(cameraLayer, name); - } - QSettings settings(PENCIL2D, PENCIL2D); - settings.setValue(SETTING_FIELD_W, dialog.getWidth()); - settings.setValue(SETTING_FIELD_H, dialog.getHeight()); - cameraLayer->setViewRect(QRect(-dialog.getWidth() / 2, -dialog.getHeight() / 2, dialog.getWidth(), dialog.getHeight())); - mEditor->view()->forceUpdateViewTransform(); -} - -void TimeLineCells::editLayerName(Layer* layer) const -{ - QRegularExpression regex("([\\x{FFEF}-\\x{FFFF}])+"); - - bool ok; - QString name = QInputDialog::getText(nullptr, tr("Layer Properties"), - tr("Layer name:"), QLineEdit::Normal, - layer->name(), &ok); - name.replace(regex, ""); - if (!ok || name.isEmpty()) - { - return; - } - - mEditor->layers()->renameLayer(layer, name); -} - -void TimeLineCells::hScrollChange(int x) -{ - mFrameOffset = x; - updateContent(); -} - -void TimeLineCells::vScrollChange(int x) -{ - mLayerOffset = x; - mScrollingVertically = true; - updateContent(); -} - -void TimeLineCells::onScrollingVerticallyStopped() -{ - mScrollingVertically = false; -} - -void TimeLineCells::setMouseMoveY(int x) -{ - mMouseMoveY = x; - updateContent(); -} - -bool TimeLineCells::trackScrubber() -{ - if (mType != TIMELINE_CELL_TYPE::Tracks || - (mPrevFrame == mEditor->currentFrame() && !mEditor->playback()->isPlaying())) - { - return false; - } - mPrevFrame = mEditor->currentFrame(); - - if (mEditor->currentFrame() <= mFrameOffset) - { - // Move the timeline back if the scrubber is offscreen to the left - mFrameOffset = mEditor->currentFrame() - 1; - emit offsetChanged(mFrameOffset); - return true; - } - else if (width() < (mEditor->currentFrame() - mFrameOffset + 1) * mFrameSize) - { - // Move timeline forward if the scrubber is offscreen to the right - if (mEditor->playback()->isPlaying()) - mFrameOffset = mFrameOffset + ((mEditor->currentFrame() - mFrameOffset) / 2); - else - mFrameOffset = mEditor->currentFrame() - width() / mFrameSize; - emit offsetChanged(mFrameOffset); - return true; - } - return false; -} - -void TimeLineCells::onDidLeaveWidget() -{ - // Reset last known frame pos to avoid wrong UI states when leaving the widget - mFramePosMoveX = 0; - update(); -} diff --git a/app/src/timelinelayercell.cpp b/app/src/timelinelayercell.cpp new file mode 100644 index 0000000000..4d060efe2b --- /dev/null +++ b/app/src/timelinelayercell.cpp @@ -0,0 +1,59 @@ +/* + +Pencil2D - Traditional Animation Software +Copyright (C) 2005-2007 Patrick Corrieri & Pascal Naidon +Copyright (C) 2008-2009 Mj Mendoza IV +Copyright (C) 2012-2020 Matthew Chiawen Chang + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; version 2 of the License. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +*/ + +#include "timelinelayercell.h" + +#include "preferencemanager.h" +#include "layermanager.h" +#include "viewmanager.h" +#include "pencilsettings.h" + +#include "layer.h" +#include "layercamera.h" +#include "camerapropertiesdialog.h" +#include "timelinelayercelleditorwidget.h" + +#include +#include +#include +#include + +#include + +TimeLineLayerCell::TimeLineLayerCell(TimeLine* timeline, + QWidget* parent, + Editor* editor, + Layer* layer, + const QPoint& origin, int width, int height) + : TimeLineBaseCell(timeline, parent, editor) +{ + mEditorWidget = new TimeLineLayerCellEditorWidget(parent, editor, layer); + mEditorWidget->setGeometry(QRect(origin, QSize(width, height))); + mEditorWidget->show(); +} + +TimeLineLayerCell::~TimeLineLayerCell() +{ + mEditorWidget->deleteLater(); +} + +void TimeLineLayerCell::setSize(const QSize& size) +{ + mEditorWidget->resize(size); +} + diff --git a/app/src/timelinelayercell.h b/app/src/timelinelayercell.h new file mode 100644 index 0000000000..185f107755 --- /dev/null +++ b/app/src/timelinelayercell.h @@ -0,0 +1,54 @@ +/* + +Pencil2D - Traditional Animation Software +Copyright (C) 2005-2007 Patrick Corrieri & Pascal Naidon +Copyright (C) 2008-2009 Mj Mendoza IV +Copyright (C) 2012-2020 Matthew Chiawen Chang + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; version 2 of the License. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +*/ + +#ifndef TIMELINELAYERCELL_H +#define TIMELINELAYERCELL_H + +#include +#include +#include + +#include "timelinebasecell.h" + +class TimeLine; +class Editor; +class Layer; +class TimeLineLayerCellEditorWidget; + +class PreferenceManager; + +class TimeLineLayerCell : public TimeLineBaseCell +{ + Q_OBJECT +public: + TimeLineLayerCell(TimeLine* timeline, + QWidget* parent, + Editor* editor, + Layer* layer, + const QPoint& origin, int width, int height); + ~TimeLineLayerCell() override; + + TimeLineLayerCellEditorWidget* editorWidget() const { return mEditorWidget; } + + void setSize(const QSize& size) override; + +private: + TimeLineLayerCellEditorWidget* mEditorWidget = nullptr; +}; + +#endif // TIMELINELAYERCELL_H diff --git a/app/src/timelinelayercelleditorwidget.cpp b/app/src/timelinelayercelleditorwidget.cpp new file mode 100644 index 0000000000..c9bd9d4710 --- /dev/null +++ b/app/src/timelinelayercelleditorwidget.cpp @@ -0,0 +1,248 @@ +/* + +Pencil2D - Traditional Animation Software +Copyright (C) 2005-2007 Patrick Corrieri & Pascal Naidon +Copyright (C) 2008-2009 Mj Mendoza IV +Copyright (C) 2012-2020 Matthew Chiawen Chang + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; version 2 of the License. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +*/ + +#include "timelinelayercelleditorwidget.h" + +#include "editor.h" +#include "timeline.h" +#include "pencilsettings.h" + +#include "layermanager.h" +#include "layercamera.h" +#include "preferencemanager.h" +#include "viewmanager.h" +#include "camerapropertiesdialog.h" +#include "layervisibilitybutton.h" +#include "lineeditwidget.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +TimeLineLayerCellEditorWidget::TimeLineLayerCellEditorWidget(QWidget* parent, + Editor* editor, + Layer* layer) + : QWidget(parent), + mEditor(editor), + mLayer(layer) +{ + + if (mLayer->type() == Layer::BITMAP) mIcon = QIcon(":icons/themes/playful/timeline/cell-bitmap.svg"); + if (mLayer->type() == Layer::VECTOR) mIcon = QIcon(":icons/themes/playful/timeline/cell-vector.svg"); + if (mLayer->type() == Layer::SOUND) mIcon = QIcon(":icons/themes/playful/timeline/cell-sound.svg"); + if (mLayer->type() == Layer::CAMERA) mIcon = QIcon(":icons/themes/playful/timeline/cell-camera.svg"); + + LayerVisibilityButton* layerVisibilityButton = new LayerVisibilityButton(this, LayerVisibilityContext::LOCAL, layer, editor); + mHBoxLayout = new QHBoxLayout(this); + + mHBoxLayout->addWidget(layerVisibilityButton); + QLabel* iconLabel = new QLabel(this); + iconLabel->setPixmap(mIcon.pixmap(mLabelIconSize)); + mHBoxLayout->addWidget(iconLabel); + mHBoxLayout->addSpacing(4); + mLayerNameEditWidget = new LineEditWidget(this, mLayer->name()); + mLayerNameEditWidget->setFocusPolicy(Qt::NoFocus); + mHBoxLayout->addWidget(mLayerNameEditWidget); + mHBoxLayout->addSpacerItem(new QSpacerItem(0, 0, QSizePolicy::Expanding, QSizePolicy::Ignored)); + mHBoxLayout->setContentsMargins(0,0,0,0); + mHBoxLayout->setSpacing(0); + + connect(layerVisibilityButton, &LayerVisibilityButton::visibilityChanged, this, &TimeLineLayerCellEditorWidget::layerVisibilityChanged); + connect(mLayerNameEditWidget, &LineEditWidget::editingFinished, this, &TimeLineLayerCellEditorWidget::onFinishedEditingName); + connect(mEditor->layers(), &LayerManager::currentLayerChanged, mLayerNameEditWidget, &LineEditWidget::deselect); +} + +void TimeLineLayerCellEditorWidget::setGeometry(const QRect& rect) +{ + QWidget::setGeometry(rect); + mHBoxLayout->setGeometry(rect); +} + +void TimeLineLayerCellEditorWidget::paintEvent(QPaintEvent*) +{ + QPainter painter(this); + QPalette palette = QApplication::palette(); + + bool isSelected = mEditor->layers()->currentLayer()->id() == mLayer->id(); + paintBackground(painter, palette, isSelected); +} + +void TimeLineLayerCellEditorWidget::paintBackground(QPainter& painter, const QPalette& palette, bool isSelected) const +{ + int x = rect().topLeft().x(); + int y = rect().topLeft().y(); + if (isSelected) + { + painter.setBrush(palette.color(QPalette::Highlight)); + } + else + { + painter.setBrush(palette.color(QPalette::Base)); + } + painter.setPen(Qt::NoPen); + painter.drawRect(x, y, size().width(), size().height()); +} + +void TimeLineLayerCellEditorWidget::mousePressEvent(QMouseEvent *event) +{ + QWidget::mousePressEvent(event); + if (mEditor->layers()->currentLayer() != mLayer) + { + mEditor->layers()->setCurrentLayer(mLayer); + mEditor->layers()->currentLayer()->deselectAll(); + } + + handleDragStarted(event); +} + +void TimeLineLayerCellEditorWidget::mouseMoveEvent(QMouseEvent *event) +{ + QWidget::mouseMoveEvent(event); + handleDragging(event); +} + +void TimeLineLayerCellEditorWidget::mouseReleaseEvent(QMouseEvent *event) +{ + QWidget::mouseReleaseEvent(event); + handleDragEnded(event); +} + +void TimeLineLayerCellEditorWidget::mouseDoubleClickEvent(QMouseEvent * event) +{ + QWidget::mouseDoubleClickEvent(event); + if (event->buttons() & Qt::LeftButton) + { + if (!isInsideLayerVisibilityArea(event)) + { + editLayerProperties(); + } + } +} + +bool TimeLineLayerCellEditorWidget::isInsideLayerVisibilityArea(QMouseEvent* event) const +{ + return event->pos().x() < 15; +} + +void TimeLineLayerCellEditorWidget::handleDragStarted(QMouseEvent* event) +{ + if (isInsideLayerVisibilityArea(event)) { + return; + } + + if (event->buttons() & Qt::LeftButton) { + if (mLayerNameEditWidget->hasFocus()) { + mLayerNameEditWidget->deselect(); + } + + mIsDraggable = true; + mDragFromY = y(); + emit drag(DragEvent::STARTED, this, 0, y()); + } +} + +void TimeLineLayerCellEditorWidget::handleDragging(QMouseEvent* event) +{ + if (event->buttons() & Qt::LeftButton && mIsDraggable) { + int delta = event->pos().y() - y(); + int mappedY = mapToParent(event->pos()).y(); + int centerY = mDragFromY + (size().height() * 0.5); + if (hasDetached(mappedY - centerY)) { + mDidDetach = true; + int newCenter = mapToParent(pos()).y() - size().height() * 0.5; + emit drag(DragEvent::DRAGGING, this, 0, newCenter + delta); + } else { + mDidDetach = false; + emit drag(DragEvent::DRAGGING, this, 0, mDragFromY); + } + } +} + +void TimeLineLayerCellEditorWidget::handleDragEnded(QMouseEvent*) +{ + if (mIsDraggable) { + emit drag(DragEvent::ENDED, this, 0, y()); + + mIsDraggable = false; + mDragFromY = y(); + mDidDetach = false; + } +} + +void TimeLineLayerCellEditorWidget::editLayerProperties() const +{ + if (mLayer->type() == Layer::CAMERA) { + editLayerProperties(static_cast(mLayer)); + } +} + +void TimeLineLayerCellEditorWidget::editLayerProperties(LayerCamera* cameraLayer) const +{ + QRegularExpression regex("([\\x{FFEF}-\\x{FFFF}])+"); + + CameraPropertiesDialog dialog(cameraLayer->name(), cameraLayer->getViewRect().width(), + cameraLayer->getViewRect().height()); + if (dialog.exec() != QDialog::Accepted) + { + return; + } + QString name = dialog.getName().replace(regex, ""); + + if (!name.isEmpty()) + { + mLayerNameEditWidget->setText(name); + mEditor->layers()->renameLayer(cameraLayer, name); + } + QSettings settings(PENCIL2D, PENCIL2D); + settings.setValue(SETTING_FIELD_W, dialog.getWidth()); + settings.setValue(SETTING_FIELD_H, dialog.getHeight()); + cameraLayer->setViewRect(QRect(-dialog.getWidth() / 2, -dialog.getHeight() / 2, dialog.getWidth(), dialog.getHeight())); + mEditor->view()->forceUpdateViewTransform(); +} + +void TimeLineLayerCellEditorWidget::onFinishedEditingName() +{ + QRegularExpression regex("([\\x{FFEF}-\\x{FFFF}])+"); + QString newName = mLayerNameEditWidget->text(); + + newName.replace(regex, ""); + if (mWarningShown) { + return; + } + + if (newName.isEmpty()) + { + mWarningShown = true; + mLayerNameEditWidget->setText(mLayer->name()); + int result = QMessageBox::information(this, tr("Empty name"), tr("The name of the layer cannot be left empty"), + QMessageBox::Ok); + + if (result == QMessageBox::Ok) { + mWarningShown = false; + } + return; + } + + mEditor->layers()->renameLayer(mLayer, newName); +} diff --git a/app/src/timelinelayercelleditorwidget.h b/app/src/timelinelayercelleditorwidget.h new file mode 100644 index 0000000000..3333cd8327 --- /dev/null +++ b/app/src/timelinelayercelleditorwidget.h @@ -0,0 +1,100 @@ +/* + +Pencil2D - Traditional Animation Software +Copyright (C) 2005-2007 Patrick Corrieri & Pascal Naidon +Copyright (C) 2008-2009 Mj Mendoza IV +Copyright (C) 2012-2020 Matthew Chiawen Chang + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; version 2 of the License. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +*/ + +#ifndef TIMELINELAYERCELLEDITORWIDGET_H +#define TIMELINELAYERCELLEDITORWIDGET_H + +#include +#include +#include +#include +#include +#include + +#include "pencildef.h" + +class TimeLine; +class Editor; +class Layer; +class LayerCamera; +class PreferenceManager; +class QHBoxLayout; +class QIcon; +class QLabel; +class LineEditWidget; + +class TimeLineLayerCellEditorWidget : public QWidget +{ + Q_OBJECT +public: + TimeLineLayerCellEditorWidget(QWidget* parent, + Editor* editor, + Layer* layer); + + void setGeometry(const QRect& rect); + + void editLayerProperties() const; + + void mousePressEvent(QMouseEvent* event) override; + void mouseMoveEvent(QMouseEvent* event) override; + void mouseReleaseEvent(QMouseEvent* event) override; + void mouseDoubleClickEvent(QMouseEvent* event) override; + void paintEvent(QPaintEvent* event) override; + + bool didDetach() const { return mDidDetach; } + const Layer* layer() const { return mLayer; } + +signals: + void drag(const DragEvent& dragEvent, TimeLineLayerCellEditorWidget* cell, int x, int y); + void layerVisibilityChanged(); + +private: + void handleDragStarted(QMouseEvent* event); + void handleDragging(QMouseEvent* event); + void handleDragEnded(QMouseEvent* event); + + bool isInsideLayerVisibilityArea(QMouseEvent* event) const; + + bool hasDetached(int yOffset) const { return abs(yOffset) > mDetachThreshold; } + + void paintBackground(QPainter& painter, const QPalette& palette, bool isSelected) const; + + void editLayerProperties(LayerCamera* cameraLayer) const; + + void onFinishedEditingName(); + + QSize mLabelIconSize = QSize(20,20); + bool mDidDetach = false; + bool mIsDraggable = false; + bool mWarningShown = false; + + LineEditWidget* mLayerNameEditWidget = nullptr; + QIcon mIcon; + + int mDetachThreshold = 5; + + Editor* mEditor = nullptr; + Layer* mLayer = nullptr; + + PreferenceManager* mPrefs = nullptr; + QHBoxLayout* mHBoxLayout = nullptr; + + int mDragFromY = 0; +}; + +#endif // TIMELINELAYERCELLEDITORWIDGET_H diff --git a/app/src/timelinelayercellgutterwidget.cpp b/app/src/timelinelayercellgutterwidget.cpp new file mode 100644 index 0000000000..6099ad048c --- /dev/null +++ b/app/src/timelinelayercellgutterwidget.cpp @@ -0,0 +1,48 @@ +/* + +Pencil2D - Traditional Animation Software +Copyright (C) 2005-2007 Patrick Corrieri & Pascal Naidon +Copyright (C) 2008-2009 Mj Mendoza IV +Copyright (C) 2012-2020 Matthew Chiawen Chang + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; version 2 of the License. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +*/ + +#include "timelinelayercellgutterwidget.h" + +#include +#include + +TimeLineLayerCellGutterWidget::TimeLineLayerCellGutterWidget(int width, QWidget* parent) + : QWidget(parent) +{ + + setGeometry(0, 0, width, 8); +} + +void TimeLineLayerCellGutterWidget::paintEvent(QPaintEvent* event) +{ + QPainter painter(this); + QPalette palette = QApplication::palette(); + + QPen pen = palette.color(QPalette::HighlightedText); + pen.setWidth(1); + painter.setPen(pen); + QColor brushColor = palette.color(QPalette::Highlight); + brushColor.setAlphaF(0.5); + painter.setBrush(brushColor); + painter.drawRect(rect().x()+1, rect().top()+1, rect().width()-1, rect().bottom()-1); +} + +void TimeLineLayerCellGutterWidget::updateWidth(int width) +{ + resize(width, height()); +} diff --git a/app/src/timelinelayercellgutterwidget.h b/app/src/timelinelayercellgutterwidget.h new file mode 100644 index 0000000000..4ed829684d --- /dev/null +++ b/app/src/timelinelayercellgutterwidget.h @@ -0,0 +1,33 @@ +/* + +Pencil2D - Traditional Animation Software +Copyright (C) 2005-2007 Patrick Corrieri & Pascal Naidon +Copyright (C) 2008-2009 Mj Mendoza IV +Copyright (C) 2012-2020 Matthew Chiawen Chang + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; version 2 of the License. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +*/ + +#ifndef TIMELINELAYERCELLGUTTERWIDGET_H +#define TIMELINELAYERCELLGUTTERWIDGET_H + +#include + +class TimeLineLayerCellGutterWidget : public QWidget +{ +public: + TimeLineLayerCellGutterWidget(int width, QWidget* parent = nullptr); + + void paintEvent(QPaintEvent* event) override; + void updateWidth(int width); +}; + +#endif // TIMELINELAYERCELLGUTTERWIDGET_H diff --git a/app/src/timelinelayerheaderwidget.cpp b/app/src/timelinelayerheaderwidget.cpp new file mode 100644 index 0000000000..c52bc44585 --- /dev/null +++ b/app/src/timelinelayerheaderwidget.cpp @@ -0,0 +1,47 @@ +/* + +Pencil2D - Traditional Animation Software +Copyright (C) 2005-2007 Patrick Corrieri & Pascal Naidon +Copyright (C) 2008-2009 Mj Mendoza IV +Copyright (C) 2012-2020 Matthew Chiawen Chang + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; version 2 of the License. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +*/ + +#include "timelinelayerheaderwidget.h" + +#include +#include +#include +#include +#include +#include +#include + +#include "editor.h" +#include "timeline.h" +#include "layervisibilitybutton.h" + +TimeLineLayerHeaderWidget::TimeLineLayerHeaderWidget(TimeLine* timeLine, + Editor* editor) + : QWidget(timeLine) +{ + mHLayout = new QHBoxLayout(this); + mVisibilityButton = new LayerVisibilityButton(this, LayerVisibilityContext::GLOBAL, nullptr, editor); + + mHLayout->setContentsMargins(1,0,0,0); + mHLayout->addWidget(mVisibilityButton); + mHLayout->addSpacerItem(new QSpacerItem(0,0, QSizePolicy::Expanding, QSizePolicy::Expanding)); +} + +TimeLineLayerHeaderWidget::~TimeLineLayerHeaderWidget() +{ +} diff --git a/app/src/timelinelayerheaderwidget.h b/app/src/timelinelayerheaderwidget.h new file mode 100644 index 0000000000..985f0df522 --- /dev/null +++ b/app/src/timelinelayerheaderwidget.h @@ -0,0 +1,46 @@ +/* + +Pencil2D - Traditional Animation Software +Copyright (C) 2005-2007 Patrick Corrieri & Pascal Naidon +Copyright (C) 2008-2009 Mj Mendoza IV +Copyright (C) 2012-2020 Matthew Chiawen Chang + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; version 2 of the License. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +*/ + +#ifndef TIMELINELAYERHEADERWIDGET_H +#define TIMELINELAYERHEADERWIDGET_H + +#include "timelinebasecell.h" + +#include +#include + + +class TimeLine; +class Editor; +class LayerVisibilityButton; +class QHBoxLayout; + +class TimeLineLayerHeaderWidget : public QWidget +{ +public: + TimeLineLayerHeaderWidget(TimeLine* timeLine, + Editor* editor); + + ~TimeLineLayerHeaderWidget() override; + +private: + LayerVisibilityButton* mVisibilityButton = nullptr; + QHBoxLayout* mHLayout = nullptr; +}; + +#endif // TIMELINELAYERHEADERWIDGET_H diff --git a/app/src/timelinelayerlist.cpp b/app/src/timelinelayerlist.cpp new file mode 100644 index 0000000000..482a218fc5 --- /dev/null +++ b/app/src/timelinelayerlist.cpp @@ -0,0 +1,247 @@ +/* + +Pencil2D - Traditional Animation Software +Copyright (C) 2005-2007 Patrick Corrieri & Pascal Naidon +Copyright (C) 2008-2009 Mj Mendoza IV +Copyright (C) 2012-2020 Matthew Chiawen Chang + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; version 2 of the License. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +*/ + +#include "timelinelayerlist.h" + +#include +#include + +#include "editor.h" +#include "layermanager.h" +#include "preferencemanager.h" +#include "timeline.h" + +#include "timelinelayercell.h" +#include "timelinelayercelleditorwidget.h" +#include "timelinelayercellgutterwidget.h" + +TimeLineLayerList::TimeLineLayerList(TimeLine* parent, Editor* editor) : QWidget(parent) +{ + mTimeLine = parent; + mEditor = editor; + mPrefs = editor->preference(); + + setMinimumSize(500, 4 * mLayerHeight); + setSizePolicy(QSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding)); + setAttribute(Qt::WA_OpaquePaintEvent, false); + setMouseTracking(true); + + connect(mPrefs, &PreferenceManager::optionChanged, this, &TimeLineLayerList::loadSetting); + + mGutterWidget = new TimeLineLayerCellGutterWidget(width(), this); +} + +TimeLineLayerList::~TimeLineLayerList() +{ +} + +void TimeLineLayerList::loadSetting(SETTING setting) +{ + updateContent(); +} + +void TimeLineLayerList::loadLayerCells() +{ + if (!mLayerCells.isEmpty()) { + for (TimeLineLayerCell* cell : qAsConst(mLayerCells)) { + // The cells might still be in use when this happens, as such + // make sure they are set for deletion later. + cell->deleteLater(); + } + mLayerCells.clear(); + } + + for (int i = 0; i < mEditor->layers()->count(); i++) + { + Layer* layeri = mEditor->layers()->getLayer(i); + const int layerY = getLayerCellY(i); + TimeLineLayerCell* cell = new TimeLineLayerCell(mTimeLine, this, mEditor, layeri, QPoint(0, layerY), width(), mLayerHeight); + mLayerCells.append(cell); + + connect(cell->editorWidget(), &TimeLineLayerCellEditorWidget::drag, this, &TimeLineLayerList::onCellDragged); + connect(cell->editorWidget(), &TimeLineLayerCellEditorWidget::layerVisibilityChanged, mTimeLine, &TimeLine::updateContent); + } + + setMinimumHeight(mEditor->layers()->count() * mLayerHeight); +} + +int TimeLineLayerList::getLayerNumber(int y) const +{ + return (mEditor->layers()->count() - 1) - (y / mLayerHeight); +} + +int TimeLineLayerList::getLayerCellY(int layerNumber) const +{ + return (mEditor->layers()->count() - 1 - layerNumber) * mLayerHeight; +} + +void TimeLineLayerList::updateContent() +{ + mRedrawContent = true; + update(); +} + +void TimeLineLayerList::drawContent() +{ + QPainter painter(&mPixmapCache); + + // grey background of the view + const QPalette palette = QApplication::palette(); + painter.setPen(Qt::NoPen); + painter.setBrush(palette.color(QPalette::Base)); + painter.drawRect(QRect(0, 0, width(), height())); + + mRedrawContent = false; +} + +void TimeLineLayerList::paintEvent(QPaintEvent*) +{ + QPainter painter(this); + + if (mPixmapCache.isNull() || mRedrawContent) + { + drawContent(); + } + if (!mPixmapCache.isNull()) + { + painter.drawPixmap(QPoint(0, 0), mPixmapCache); + } +} + +void TimeLineLayerList::resizeEvent(QResizeEvent* event) +{ + if (event->size() != mPixmapCache.size()) { + mPixmapCache = QPixmap(event->size() * devicePixelRatioF()); + mPixmapCache.fill(Qt::transparent); + mPixmapCache.setDevicePixelRatio(this->devicePixelRatioF()); + } + setMinimumHeight(mEditor->layers()->count() * mLayerHeight);; + + const QList layerCells = mLayerCells; + for (TimeLineLayerCell* cell : layerCells) { + if (layerCells.isDetached()) { return; } + cell->setSize(QSize(event->size().width(), mLayerHeight)); + } + + mGutterWidget->updateWidth(event->size().width()); + + updateContent(); + event->accept(); + +} + +int TimeLineLayerList::getLayerGutterYPosition(int posY) const +{ + int layerNumber = getLayerNumber(posY + (mLayerHeight * 0.5)); + + if(posY > getLayerCellY(layerNumber)) { + layerNumber--; + } + + int maxLayerNum = mEditor->layers()->count() - 1; + if (layerNumber > maxLayerNum) { + layerNumber = maxLayerNum; + } else if (layerNumber < 0) { + layerNumber = 0; + } + return getLayerCellY(layerNumber); +} + +void TimeLineLayerList::onCellDragged(const DragEvent& event, TimeLineLayerCellEditorWidget* editorWidget, int /*x*/, int y) +{ + switch (event) + { + case DragEvent::STARTED: { + mGutterPositionY = getLayerGutterYPosition(y); + mFromLayer = getLayerNumber(y); + editorWidget->raise(); + mGutterWidget->hide(); + mGutterWidget->raise(); + emit cellDraggedY(event, y); + break; + } + case DragEvent::DRAGGING: { + + // The camera layer is at the bottom and must not be moved + if (mFromLayer <= 0) { + break; + } + + editorWidget->move(0, y); + mGutterPositionY = getLayerGutterYPosition(y); + + if (editorWidget->didDetach()) { + + int dragToNumber = getDragToLayerNumber(getLayerCellY(mFromLayer), mGutterPositionY); + + if (dragToNumber != mFromLayer && mFromLayer > 0) { + mGutterWidget->show(); + } else { + mGutterWidget->hide(); + } + } + + mGutterWidget->move(0, mGutterPositionY - mGutterWidget->rect().center().y()); + emit cellDraggedY(event, y); + break; + } + case DragEvent::ENDED: { + int fromLayerDragY = getLayerCellY(mFromLayer); + int dragToNumber = getDragToLayerNumber(getLayerCellY(mFromLayer), mGutterPositionY); + + if (dragToNumber != mFromLayer && dragToNumber > -1 && mGutterWidget->isVisible()) + { + if (dragToNumber < mEditor->layers()->count()) + { + // Bubble the from layer up or down to the to layer + if (dragToNumber < mFromLayer) // bubble up + { + for (int i = mFromLayer - 1; i >= dragToNumber; i--) + mEditor->layers()->swapLayers(i, i + 1); + } + else // bubble down + { + for (int i = mFromLayer + 1; i <= dragToNumber; i++) + mEditor->layers()->swapLayers(i, i - 1); + } + } + } + mGutterWidget->hide(); + editorWidget->move(0, fromLayerDragY); + emit cellDraggedY(event, y); + mGutterPositionY = -1; + mFromLayer = -1; + break; + } + } + + updateContent(); +} + +int TimeLineLayerList::getDragToLayerNumber(int fromLayerDragY, int gutterPositionY) const +{ + if (fromLayerDragY > gutterPositionY) { + // If we're starting from below, adjust the drag number so we're one cell below + return getLayerNumber(gutterPositionY + (mLayerHeight * 0.5)); + } else if (fromLayerDragY < gutterPositionY) { + // If we're starting from above, adjust the drag number so we're one cell above + return getLayerNumber(gutterPositionY - (mLayerHeight * 0.5)); + } + + return getLayerNumber(fromLayerDragY); +} diff --git a/app/src/timelinelayerlist.h b/app/src/timelinelayerlist.h new file mode 100644 index 0000000000..653b8f73cc --- /dev/null +++ b/app/src/timelinelayerlist.h @@ -0,0 +1,100 @@ +/* + +Pencil2D - Traditional Animation Software +Copyright (C) 2005-2007 Patrick Corrieri & Pascal Naidon +Copyright (C) 2008-2009 Mj Mendoza IV +Copyright (C) 2012-2020 Matthew Chiawen Chang + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; version 2 of the License. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +*/ + +#ifndef TIMELINELAYERLIST_H +#define TIMELINELAYERLIST_H + +#include +#include +#include +#include +#include + +#include "timelinelayercell.h" + +enum class LayerVisibility; +enum class SETTING; + +class Layer; +class TimeLine; +class Editor; +class PreferenceManager; +class TimeLineLayerHeaderWidget; + +class QScrollArea; +class QPaintEvent; +class QMouseEvent; +class QResizeEvent; +class QListWidget; +class TimeLineLayerCellGutterWidget; + +class TimeLineLayerList : public QWidget +{ + Q_OBJECT + +public: + TimeLineLayerList( TimeLine* parent, Editor* editor); + ~TimeLineLayerList() override; + + int getLayerHeight() const { return mLayerHeight; } + + int getLayerGutterYPosition(int posY) const; + + void loadLayerCells(); + void onCellDragged(const DragEvent& event, TimeLineLayerCellEditorWidget* editorWidget, int, int y); + +signals: + void cellDraggedY(const DragEvent& event, int y); + void offsetChanged(int); + +public slots: + void updateContent(); + +protected: + void paintEvent(QPaintEvent* event) override; + void resizeEvent(QResizeEvent* event) override; + +private slots: + void loadSetting(SETTING setting); + +private: + + int getLayerNumber(int y) const; + int getLayerCellY(int layerNumber) const; + + int getDragToLayerNumber(int y, int gutterPositionY) const; + + void drawContent(); + + TimeLine* mTimeLine = nullptr; + Editor* mEditor = nullptr; // the editor for which this timeLine operates + PreferenceManager* mPrefs = nullptr; + + QPixmap mPixmapCache; + + int mGutterPositionY = -1; + int mFromLayer = 0; + + bool mRedrawContent = false; + int mLayerHeight = 20; + + QList mLayerCells; + TimeLineLayerCellGutterWidget* mGutterWidget = nullptr; +}; + +#endif // TIMELINELAYERLIST_H diff --git a/app/src/timelinetrackheaderwidget.cpp b/app/src/timelinetrackheaderwidget.cpp new file mode 100644 index 0000000000..39a4d0bbdb --- /dev/null +++ b/app/src/timelinetrackheaderwidget.cpp @@ -0,0 +1,271 @@ +/* + +Pencil2D - Traditional Animation Software +Copyright (C) 2005-2007 Patrick Corrieri & Pascal Naidon +Copyright (C) 2008-2009 Mj Mendoza IV +Copyright (C) 2012-2020 Matthew Chiawen Chang + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; version 2 of the License. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +*/ + +#include "timelinetrackheaderwidget.h" + +#include "editor.h" +#include "timeline.h" + +#include "preferencemanager.h" +#include "layermanager.h" +#include "layer.h" +#include "playbackmanager.h" + +#include +#include +#include + +TimeLineTrackHeaderWidget::TimeLineTrackHeaderWidget(TimeLine* timeLine, Editor* editor) + : QWidget(timeLine), + mEditor(editor), + mTimeLine(timeLine) +{ + + mFrameSize = mEditor->preference()->getInt(SETTING::FRAME_SIZE); + mShortScrub = mEditor->preference()->isOn(SETTING::SHORT_SCRUB); + + connect(mEditor->preference(), &PreferenceManager::optionChanged, this, &TimeLineTrackHeaderWidget::onSettingChanged); +} + +void TimeLineTrackHeaderWidget::paintEvent(QPaintEvent*) +{ + QPainter painter(this); + + const QPalette palette = QApplication::palette(); + paintBackground(painter, palette); + if (!mEditor->playback()->isPlaying()) { + paintOnionSkin(painter); + } + paintTicks(painter, palette); + paintSplitter(painter, palette); + + paintScrubber(painter, palette); +} + +void TimeLineTrackHeaderWidget::onSettingChanged(SETTING setting) +{ + switch (setting) { + case SETTING::FRAME_SIZE: + mFrameSize = mEditor->preference()->getInt(setting); + break; + case SETTING::SHORT_SCRUB: + mShortScrub = mEditor->preference()->isOn(setting); + break; + default: + break; + } + + update(); +} + +void TimeLineTrackHeaderWidget::paintBackground(QPainter& painter, const QPalette& palette) const +{ + painter.setPen(Qt::NoPen); + painter.setBrush(palette.color(QPalette::Base)); + painter.drawRect(QRect(0, 0, width() - 1, 0)); +} + +void TimeLineTrackHeaderWidget::paintSplitter(QPainter& painter, const QPalette& palette) const +{ + // --- draw bottom line splitter for track bar + QPen pen; + pen.setBrush(palette.color(QPalette::Mid)); + pen.setWidthF(1); + painter.setPen(pen); + painter.drawLine(0, rect().bottom(), width() - 1, rect().bottom()); +} + +void TimeLineTrackHeaderWidget::paintOnionSkin(QPainter& painter) const +{ + Layer* layer = mEditor->layers()->currentLayer(); + if (layer == nullptr) { return; } + + int frameNumber = mEditor->currentFrame(); + + int prevOnionSkinCount = mEditor->preference()->getInt(SETTING::ONION_PREV_FRAMES_NUM); + int nextOnionSkinCount = mEditor->preference()->getInt(SETTING::ONION_NEXT_FRAMES_NUM); + + bool isAbsolute = (mEditor->preference()->getString(SETTING::ONION_TYPE) == "absolute"); + + if (mEditor->preference()->isOn(SETTING::PREV_ONION) && prevOnionSkinCount > 0) + { + int onionFrameNumber = frameNumber; + if (isAbsolute) + { + onionFrameNumber = layer->getPreviousFrameNumber(onionFrameNumber+1, true); + } + onionFrameNumber = layer->getPreviousFrameNumber(onionFrameNumber, isAbsolute); + int onionPosition = 0; + + while (onionPosition < prevOnionSkinCount && onionFrameNumber > 0) + { + painter.setBrush(QColor(128, 128, 128, 128)); + painter.setPen(Qt::NoPen); + QRect onionRect; + int frameX = getFrameX(onionFrameNumber); + onionRect.setTopLeft(QPoint(frameX, 0)); + onionRect.setBottomRight(QPoint(frameX + mFrameSize, height())); + painter.drawRect(onionRect); + + onionFrameNumber = layer->getPreviousFrameNumber(onionFrameNumber, isAbsolute); + onionPosition++; + } + } + + if (mEditor->preference()->isOn(SETTING::NEXT_ONION) && nextOnionSkinCount > 0) { + + int onionFrameNumber = layer->getNextFrameNumber(frameNumber, isAbsolute); + int onionPosition = 0; + + while (onionPosition < nextOnionSkinCount && onionFrameNumber > 0) + { + painter.setBrush(QColor(128, 128, 128, 128)); + painter.setPen(Qt::NoPen); + QRect onionRect; + int frameX = getFrameX(onionFrameNumber); + onionRect.setTopLeft(QPoint(frameX, 0)); + onionRect.setBottomRight(QPoint(frameX + mFrameSize, height())); + painter.drawRect(onionRect); + + onionFrameNumber = layer->getNextFrameNumber(onionFrameNumber, isAbsolute); + onionPosition++; + } + } +} + +void TimeLineTrackHeaderWidget::paintScrubber(QPainter &painter, const QPalette &palette) const +{ + int currentFrame = mEditor->currentFrame(); + // --- draw the position of the current frame + if (currentFrame > mScrollOffsetX) + { + QColor scrubColor = palette.color(QPalette::Highlight); + scrubColor.setAlpha(160); + painter.setBrush(scrubColor); + painter.setPen(Qt::NoPen); + + int currentFrameX = getFrameX(currentFrame); + QRect scrubRect; + scrubRect.setTopLeft(QPoint(currentFrameX, 0)); + scrubRect.setBottomRight(QPoint(currentFrameX + mFrameSize, height())); + painter.save(); + painter.drawRect(scrubRect); + painter.restore(); + + painter.setPen(palette.color(QPalette::HighlightedText)); + int incr = (currentFrame < 10) ? 4 : 0; + painter.drawText(QPoint(currentFrameX + incr, 15), + QString::number(currentFrame)); + } +} + +void TimeLineTrackHeaderWidget::paintTicks(QPainter& painter, const QPalette& palette) const +{ + painter.setPen(palette.color(QPalette::Text)); + painter.setBrush(palette.brush(QPalette::Text)); + int fps = mEditor->playback()->fps(); + for (int i = mScrollOffsetX; i < mScrollOffsetX + (width()) / mFrameSize; i++) + { + // line x pos + some offset + const int lineX = getFrameX(i) + 1; + if (i + 1 >= mTimeLine->getRangeLower() && i < mTimeLine->getRangeUpper()) + { + painter.setPen(Qt::NoPen); + painter.setBrush(palette.color(QPalette::Highlight)); + + painter.drawRect(lineX, 1, mFrameSize + 1, 2); + + painter.setPen(palette.color(QPalette::Text)); + painter.setBrush(palette.brush(QPalette::Text)); + } + + // Draw large tick at fps mark + if (i % fps == 0 || i % fps == fps / 2) + { + painter.drawLine(lineX, 1, lineX, 5); + } + else // draw small tick + { + painter.drawLine(lineX, 1, lineX, 3); + } + if (i == 0 || i % fps == fps - 1) + { + int incr = (i < 9) ? 4 : 0; // poor man’s text centering + painter.drawText(QPoint(lineX + incr, 15), QString::number(i + 1)); + } + } +} + +void TimeLineTrackHeaderWidget::mousePressEvent(QMouseEvent *event) +{ + QWidget::mousePressEvent(event); + if (event->buttons() & Qt::LeftButton) { + mEditor->scrubTo(getFrameNumber(event->pos().x())); + } +} + +void TimeLineTrackHeaderWidget::mouseMoveEvent(QMouseEvent* event) +{ + QWidget::mouseMoveEvent(event); + if (event->buttons() & Qt::LeftButton) { + if (mEditor->currentFrame() > 0) { + mTimeLine->scrubbing = true; + } + if (mTimeLine->scrubbing) + { + if (mEditor->playback()->isPlaying()) + { + mEditor->playback()->stop(); + } + mEditor->scrubTo(getFrameNumber(event->pos().x())); + } + } +} + +void TimeLineTrackHeaderWidget::mouseDoubleClickEvent(QMouseEvent* event) +{ + QWidget::mouseDoubleClickEvent(event); + if (event->buttons() & Qt::LeftButton) { + // -- short scrub -- + mEditor->preference()->set(SETTING::SHORT_SCRUB, !mShortScrub); + } +} + +void TimeLineTrackHeaderWidget::mouseReleaseEvent(QMouseEvent* event) +{ + QWidget::mouseReleaseEvent(event); + mTimeLine->scrubbing = false; +} + +int TimeLineTrackHeaderWidget::getFrameNumber(int x) const +{ + return mScrollOffsetX + 1 + (x) / mFrameSize; +} + +int TimeLineTrackHeaderWidget::getFrameX(int frameNumber) const +{ + return ((frameNumber - mScrollOffsetX) * mFrameSize) - mFrameSize; +} + +void TimeLineTrackHeaderWidget::onHScrollChange(int x) +{ + mScrollOffsetX = x; + update(); +} + + diff --git a/app/src/timelinetrackheaderwidget.h b/app/src/timelinetrackheaderwidget.h new file mode 100644 index 0000000000..0f13295703 --- /dev/null +++ b/app/src/timelinetrackheaderwidget.h @@ -0,0 +1,64 @@ +/* + +Pencil2D - Traditional Animation Software +Copyright (C) 2005-2007 Patrick Corrieri & Pascal Naidon +Copyright (C) 2008-2009 Mj Mendoza IV +Copyright (C) 2012-2020 Matthew Chiawen Chang + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; version 2 of the License. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +*/ + +#ifndef TIMELINETRACKHEADERWIDGET_H +#define TIMELINETRACKHEADERWIDGET_H + +#include +#include + +#include "preferencesdef.h" + +class Editor; +class TimeLine; + +class TimeLineTrackHeaderWidget : public QWidget +{ +public: + TimeLineTrackHeaderWidget(TimeLine* timeLine, Editor* editor); + + void onSettingChanged(SETTING setting); + void onHScrollChange(int x); + +protected: + void paintEvent(QPaintEvent* event) override; + void mousePressEvent(QMouseEvent *event) override; + void mouseMoveEvent(QMouseEvent* event) override; + void mouseReleaseEvent(QMouseEvent* event) override; + void mouseDoubleClickEvent(QMouseEvent* event) override; + +private: + void paintOnionSkin(QPainter& painter) const; + void paintBackground(QPainter& painter, const QPalette& palette) const; + void paintTicks(QPainter& painter, const QPalette& palette) const; + void paintScrubber(QPainter& painter, const QPalette& palette) const; + void paintSplitter(QPainter& painter, const QPalette& palette) const; + + int getFrameNumber(int x) const; + int getFrameX(int frameNumber) const; + + int mFrameSize = 10; + Editor* mEditor = nullptr; + TimeLine* mTimeLine = nullptr; + + bool mShortScrub = false; + + int mScrollOffsetX = 0; +}; + +#endif // TIMELINETRACKHEADERWIDGET_H diff --git a/app/src/timelinetracklist.cpp b/app/src/timelinetracklist.cpp new file mode 100644 index 0000000000..9308efa06e --- /dev/null +++ b/app/src/timelinetracklist.cpp @@ -0,0 +1,831 @@ +/* + +Pencil2D - Traditional Animation Software +Copyright (C) 2005-2007 Patrick Corrieri & Pascal Naidon +Copyright (C) 2012-2020 Matthew Chiawen Chang + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; version 2 of the License. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +*/ + +#include "timelinetracklist.h" + +#include +#include +#include +#include +#include +#include + +#include "camerapropertiesdialog.h" +#include "layercamera.h" +#include "editor.h" +#include "keyframe.h" +#include "layermanager.h" +#include "viewmanager.h" +#include "object.h" +#include "playbackmanager.h" +#include "preferencemanager.h" +#include "timeline.h" + +#include "cameracontextmenu.h" + +TimeLineTrackList::TimeLineTrackList(TimeLine* parent, Editor* editor) : QWidget(parent) +{ + mTimeLine = parent; + mEditor = editor; + mPrefs = editor->preference(); + + mFrameLength = mPrefs->getInt(SETTING::TIMELINE_SIZE); + mFrameSize = mPrefs->getInt(SETTING::FRAME_SIZE); + mbShortScrub = mPrefs->isOn(SETTING::SHORT_SCRUB); + mDrawFrameNumber = mPrefs->isOn(SETTING::DRAW_LABEL); + + setMinimumSize(500, 4 * mLayerHeight); + setSizePolicy(QSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding)); + setAttribute(Qt::WA_OpaquePaintEvent, false); + setMouseTracking(true); + + connect(mPrefs, &PreferenceManager::optionChanged, this, &TimeLineTrackList::loadSetting); + connect(mEditor->layers(), &LayerManager::layerCountChanged, this, &TimeLineTrackList::onLayerCountChanged); +} + +TimeLineTrackList::~TimeLineTrackList() +{ + delete mCache; +} + +void TimeLineTrackList::loadSetting(SETTING setting) +{ + switch (setting) + { + case SETTING::TIMELINE_SIZE: + mFrameLength = mPrefs->getInt(SETTING::TIMELINE_SIZE); + mTimeLine->updateLength(); + break; + case SETTING::FRAME_SIZE: + mFrameSize = mPrefs->getInt(SETTING::FRAME_SIZE); + mTimeLine->updateLength(); + break; + case SETTING::SHORT_SCRUB: + mbShortScrub = mPrefs->isOn(SETTING::SHORT_SCRUB); + break; + case SETTING::DRAW_LABEL: + mDrawFrameNumber = mPrefs->isOn(SETTING::DRAW_LABEL); + break; + default: + break; + } + + updateContent(); +} + +void TimeLineTrackList::onLayerCountChanged(int count) +{ + setMinimumHeight(mLayerHeight * count); +} + +int TimeLineTrackList::getFrameNumber(int x) const +{ + return mFrameOffset + 1 + (x) / mFrameSize; +} + +int TimeLineTrackList::getFrameX(int frameNumber) const +{ + return (frameNumber - mFrameOffset) * mFrameSize; +} + +void TimeLineTrackList::setFrameSize(int size) +{ + mFrameSize = size; + mPrefs->set(SETTING::FRAME_SIZE, mFrameSize); + updateContent(); +} + +int TimeLineTrackList::getCellNumber(int y) const +{ + int layerNumber = y / mLayerHeight; + + int totalLayerCount = mEditor->layers()->count(); + + // Layers numbers are displayed in descending order + // The last row is layer 0 + if (layerNumber <= totalLayerCount) + layerNumber = (totalLayerCount - 1) - layerNumber; + else + layerNumber = 0; + + if (y < 0) + { + layerNumber = -1; + } + + if (layerNumber >= totalLayerCount) + { + layerNumber = totalLayerCount; + } + + //If the mouse release event if fired with mouse off the frame of the application + // mEditor->object()->getLayerCount() doesn't return the correct value. + if (layerNumber < -1) + { + layerNumber = -1; + } + return layerNumber; +} + +int TimeLineTrackList::getCellY(int layerNumber) const +{ + return (mEditor->layers()->count() - 1 - layerNumber)*mLayerHeight; +} + +void TimeLineTrackList::updateFrame(int frameNumber) +{ + int x = getFrameX(frameNumber); + update(x - mFrameSize, 0, mFrameSize + 1, height()); +} + +void TimeLineTrackList::updateContent() +{ + mRedrawContent = true; + update(); +} + +void TimeLineTrackList::showCameraMenu(QPoint pos) +{ + int frameNumber = getFrameNumber(pos.x()); + + const Layer* curLayer = mEditor->layers()->currentLayer(); + Q_ASSERT(curLayer); + + // only show menu if on camera layer and key exists + if (curLayer->type() != Layer::CAMERA || !curLayer->keyExists(frameNumber)) + { + return; + } + + mHighlightFrameEnabled = true; + mHighlightedFrame = frameNumber; + + CameraContextMenu menu(frameNumber, static_cast(curLayer)); + + menu.connect(&menu, &CameraContextMenu::aboutToHide, this, [=] { + mHighlightFrameEnabled = false; + mHighlightedFrame = -1; + update(); + + KeyFrame* key = curLayer->getKeyFrameAt(frameNumber); + if (key->isModified()) { + emit mEditor->frameModified(frameNumber); + } + }); + + // Update needs to happen before executing menu, otherwise paint event might be postponed + update(); + + menu.exec(mapToGlobal(pos)); +} + +void TimeLineTrackList::drawContent() +{ + if (mCache == nullptr) + { + mCache = new QPixmap(size()); + if (mCache->isNull()) + { + // fail to create cache + return; + } + } + + QPainter painter(mCache); + + // grey background of the view + const QPalette palette = QApplication::palette(); + painter.setPen(Qt::NoPen); + painter.setBrush(palette.color(QPalette::Base)); + painter.drawRect(QRect(0, 0, width(), height())); + + const int widgetWidth = width(); + + // Draw non-current layers + const Object* object = mEditor->object(); + Q_ASSERT(object != nullptr); + for (int i = 0; i < object->getLayerCount(); i++) + { + if (i == mEditor->layers()->currentLayerIndex()) + { + continue; + } + const Layer* layeri = object->getLayer(i); + + if (layeri != nullptr) + { + const int layerY = getCellY(i); + paintTrack(painter, layeri, 0, + layerY, widgetWidth, + mLayerHeight, false, mFrameSize); + } + } + + // Draw current layer + const Layer* currentLayer = mEditor->layers()->currentLayer(); + if (mDragEvent == DragEvent::DRAGGING) + { + paintTrack(painter, currentLayer, + 0, mCellDragY, + widgetWidth, mLayerHeight, + true, mFrameSize); + } + else + { + paintTrack(painter, + currentLayer, + 0, + getCellY(mEditor->layers()->currentLayerIndex()), + widgetWidth, + mLayerHeight, + true, + mFrameSize); + } + + for (int i = 0; i < object->getLayerCount(); i++) { + paintSelectedFrames(painter, object->getLayer(i), i); + } + mRedrawContent = false; +} + +void TimeLineTrackList::paintTrack(QPainter& painter, const Layer* layer, + int x, int y, int width, int height, + bool selected, int frameSize) const +{ + const QPalette palette = QApplication::palette(); + QColor col; + // Color each track according to the layer type + if (layer->type() == Layer::BITMAP) col = QColor(51, 155, 252); + if (layer->type() == Layer::VECTOR) col = QColor(70, 205, 123); + if (layer->type() == Layer::SOUND) col = QColor(255, 141, 112); + if (layer->type() == Layer::CAMERA) col = QColor(253, 202, 92); + // Dim invisible layers + if (!layer->visible()) col.setAlpha(64); + + painter.save(); + painter.setBrush(col); + painter.setPen(QPen(QBrush(palette.color(QPalette::Mid)), 1, Qt::SolidLine, Qt::RoundCap, Qt::RoundJoin)); + painter.drawRect(x, y, width, height); + + if (!layer->visible()) + { + painter.restore(); + return; + } + + // Changes the appearance if selected + if (selected) + { + paintSelection(painter, x, y, width, height); + } + else + { + painter.save(); + QLinearGradient linearGrad(QPointF(0, y), QPointF(0, y + height)); + linearGrad.setColorAt(0, QColor(255,255,255,150)); + linearGrad.setColorAt(1, QColor(0,0,0,0)); + painter.setCompositionMode(QPainter::CompositionMode_Overlay); + painter.setBrush(linearGrad); + painter.drawRect(x, y, width, height); + painter.restore(); + } + + paintFrames(painter, col, layer, y, height, selected, frameSize); + + painter.restore(); +} + +void TimeLineTrackList::paintFrames(QPainter& painter, QColor trackCol, const Layer* layer, int y, int height, bool selected, int frameSize) const +{ + painter.setPen(QPen(QBrush(QColor(40, 40, 40)), 1, Qt::SolidLine, Qt::RoundCap, Qt::RoundJoin)); + + int recTop = y + 1; + int standardWidth = frameSize - 2; + + int recHeight = height - 4; + + const QList selectedFrames = layer->getSelectedFramesByPos(); + layer->foreachKeyFrame([&](KeyFrame* key) + { + int framePos = key->pos(); + int recWidth = standardWidth; + int recLeft = getFrameX(framePos) - recWidth; + + // Selected frames are painted separately + if (selectedFrames.contains(framePos)) { + return; + } + + if (key->length() > 1) + { + // This is especially for sound clips. + // Sound clips are the only type of KeyFrame with variable frame length. + recWidth = frameSize * key->length(); + } + + // Paint the frame border + painter.setPen(QPen(QBrush(QColor(40, 40, 40)), 1, Qt::SolidLine, Qt::RoundCap, Qt::RoundJoin)); + + // Paint the frame contents + if (selected) + { + painter.setBrush(QColor(trackCol.red(), trackCol.green(), trackCol.blue(), 150)); + } + + painter.drawRect(recLeft, recTop, recWidth, recHeight); + }); +} + +void TimeLineTrackList::paintCurrentFrameBorder(QPainter &painter, int recLeft, int recTop, int recWidth, int recHeight) const +{ + painter.save(); + painter.setBrush(Qt::NoBrush); + painter.setPen(Qt::white); + painter.drawRect(recLeft, recTop, recWidth, recHeight); + painter.restore(); +} + +void TimeLineTrackList::paintFrameCursorOnCurrentLayer(QPainter &painter, int recTop, int recWidth, int recHeight) const +{ + int recLeft = getFrameX(mFramePosMoveX) - recWidth; + + painter.save(); + const QPalette palette = QApplication::palette(); + // Don't fill + painter.setBrush(Qt::NoBrush); + // paint border + QColor penColor = palette.color(QPalette::WindowText); + penColor.setAlpha(127); + painter.setPen(penColor); + painter.drawRect(recLeft, recTop, recWidth, recHeight); + painter.restore(); +} + +void TimeLineTrackList::paintHighlightedFrame(QPainter& painter, int framePos, int recTop, int recWidth, int recHeight) const +{ + int recLeft = getFrameX(framePos) - recWidth; + + painter.save(); + const QPalette palette = QApplication::palette(); + painter.setBrush(palette.color(QPalette::Window)); + painter.setPen(palette.color(QPalette::WindowText)); + + // Draw a rect slighly smaller than the frame + painter.drawRect(recLeft+1, recTop+1, recWidth-1, recHeight-1); + painter.restore(); +} + +void TimeLineTrackList::paintSelectedFrames(QPainter& painter, const Layer* layer, const int layerIndex) const +{ + int mouseX = mMouseMoveX; + int posUnderCursor = getFrameNumber(mMousePressX); + int standardWidth = mFrameSize - 2; + int recWidth = standardWidth; + int recHeight = mLayerHeight - 4; + int recTop = getCellY(layerIndex) + 1; + + painter.save(); + for (int framePos : layer->getSelectedFramesByPos()) { + + KeyFrame* key = layer->getKeyFrameAt(framePos); + if (key->length() > 1) + { + // This is a special case for sound clip. + // Sound clip is the only type of KeyFrame that has variable frame length. + recWidth = mFrameSize * key->length(); + } + + painter.setBrush(QColor(60, 60, 60)); + painter.setPen(QPen(QBrush(QColor(40, 40, 40)), 1, Qt::SolidLine, Qt::RoundCap, Qt::RoundJoin)); + + int frameX = getFrameX(framePos); + if (mMovingFrames) { + int offset = (framePos - posUnderCursor) + mFrameOffset; + int newFrameX = getFrameX(getFrameNumber(getFrameX(offset)+mouseX)) - standardWidth; + // Paint as frames are hovering + painter.drawRect(newFrameX, recTop-4, recWidth, recHeight); + + } else { + int currentFrameX = frameX - standardWidth; + painter.drawRect(currentFrameX, recTop, recWidth, recHeight); + } + } + painter.restore(); +} + +void TimeLineTrackList::paintSelection(QPainter& painter, int x, int y, int width, int height) const +{ + QLinearGradient linearGrad(QPointF(0, y), QPointF(0, y + height)); + linearGrad.setColorAt(0, QColor(0, 0, 0, 255)); + linearGrad.setColorAt(1, QColor(255, 255, 255, 0)); + painter.save(); + painter.setCompositionMode(QPainter::CompositionMode_Overlay); + painter.setBrush(linearGrad); + painter.setPen(Qt::NoPen); + painter.drawRect(x, y, width, height - 1); + painter.restore(); +} + +void TimeLineTrackList::paintEvent(QPaintEvent*) +{ + const QPalette palette = QApplication::palette(); + QPainter painter(this); + + if (mCache == nullptr || mRedrawContent || trackScrubber()) + { + drawContent(); + } + if (mCache) + { + painter.drawPixmap(QPoint(0, 0), *mCache); + } + + int currentFrame = mEditor->currentFrame(); + Layer* currentLayer = mEditor->layers()->currentLayer(); + KeyFrame* keyFrame = currentLayer->getKeyFrameWhichCovers(currentFrame); + if (keyFrame != nullptr) + { + int recWidth = keyFrame->length() == 1 ? mFrameSize - 2 : mFrameSize * keyFrame->length(); + int recLeft = getFrameX(keyFrame->pos()) - (mFrameSize - 2); + paintCurrentFrameBorder(painter, recLeft, getCellY(mEditor->currentLayerIndex()) + 1, recWidth, mLayerHeight - 4); + } + + if (!mMovingFrames && mLayerPosMoveY != -1 && mLayerPosMoveY == mEditor->currentLayerIndex()) + { + // This is terrible but well... + int recTop = getCellY(mLayerPosMoveY) + 1; + int standardWidth = mFrameSize - 2; + int recHeight = mLayerHeight - 4; + + if (mHighlightFrameEnabled) + { + paintHighlightedFrame(painter, mHighlightedFrame, recTop, standardWidth, recHeight); + } + if (currentLayer->visible()) + { + paintFrameCursorOnCurrentLayer(painter, recTop, standardWidth, recHeight); + } + } + + // --- draw the position of the current frame + if (currentFrame > mFrameOffset) + { + if (!mbShortScrub) { + + QColor scrubColor = palette.color(QPalette::Highlight); + scrubColor.setAlpha(160); + painter.setBrush(scrubColor); + painter.setPen(Qt::NoPen); + + int currentFrameStartX = getFrameX(currentFrame - 1) + 1; + int currentFrameEndX = getFrameX(currentFrame); + QRect scrubRect; + scrubRect.setTopLeft(QPoint(currentFrameStartX, 0)); + scrubRect.setBottomRight(QPoint(currentFrameEndX, height())); + painter.save(); + + bool mouseUnderScrubber = currentFrame == mFramePosMoveX; + if (mouseUnderScrubber) { + QRect smallScrub = QRect(QPoint(currentFrameStartX, 0), QPoint(currentFrameEndX,19)); + QPen pen = scrubColor; + pen.setWidth(2); + painter.setPen(pen); + painter.drawRect(smallScrub); + painter.setBrush(Qt::NoBrush); + } + painter.drawRect(scrubRect); + painter.restore(); + } + } +} + +void TimeLineTrackList::resizeEvent(QResizeEvent* event) +{ + + if (event->size().height() > minimumHeight()) { + setMinimumHeight(event->size().height()); + } + clearCache(); + updateContent(); + event->accept(); + emit lengthChanged(getFrameLength()); +} + +bool TimeLineTrackList::event(QEvent* event) +{ + if (event->type() == QEvent::Leave) { + onDidLeaveWidget(); + } + + return QWidget::event(event); +} + +void TimeLineTrackList::mousePressEvent(QMouseEvent* event) +{ + int frameNumber = getFrameNumber(event->pos().x()); + int cellNumber = getCellNumber(event->pos().y()); + + mMousePressX = event->pos().x(); + + mStartFrameNumber = frameNumber; + mLastFrameNumber = mStartFrameNumber; + + mCanMoveFrame = false; + mMovingFrames = false; + + mCanBoxSelect = false; + mBoxSelecting = false; + + mClickSelecting = false; + + primaryButton = event->button(); + + if (event->button() == Qt::MiddleButton) + { + mLastFrameNumber = getFrameNumber(event->pos().x()); + } + else + { + if ((cellNumber != -1) && cellNumber < mEditor->object()->getLayerCount()) + { + int previousLayerNumber = mEditor->layers()->currentLayerIndex(); + + if (previousLayerNumber != cellNumber) + { + Layer *previousLayer = mEditor->object()->getLayer(previousLayerNumber); + previousLayer->deselectAll(); + emit mEditor->selectedFramesChanged(); + mEditor->layers()->setCurrentLayer(cellNumber); + } + + Layer *currentLayer = mEditor->object()->getLayer(cellNumber); + + // Check if we are using the alt key + if (event->modifiers() == Qt::AltModifier) + { + // If it is the case, we select everything that is after the selected frame + mClickSelecting = true; + mCanMoveFrame = true; + + currentLayer->selectAllFramesAfter(frameNumber); + emit mEditor->selectedFramesChanged(); + } + // Check if we are clicking on a non selected frame + else if (!currentLayer->isFrameSelected(frameNumber)) + { + // If it is the case, we select it if it is the left button... + mCanBoxSelect = true; + mClickSelecting = true; + if (event->button() == Qt::LeftButton) + { + + if (event->modifiers() == Qt::ControlModifier) + { + // Add/remove from already selected + currentLayer->toggleFrameSelected(frameNumber, true); + emit mEditor->selectedFramesChanged(); + } + else if (event->modifiers() == Qt::ShiftModifier) + { + // Select a range from the last selected + currentLayer->extendSelectionTo(frameNumber); + emit mEditor->selectedFramesChanged(); + } + else + { + // Only select if left button clicked + currentLayer->toggleFrameSelected(frameNumber, false); + emit mEditor->selectedFramesChanged(); + } + } + + // ... or we show the camera context menu, if it is the right button + if (event->button() == Qt::RightButton) + { + showCameraMenu(event->pos()); + } + + } + else + { + // If selected they can also be interpolated + if (event->button() == Qt::RightButton) + { + showCameraMenu(event->pos()); + } + // We clicked on a selected frame, we can move it + mCanMoveFrame = true; + } + + if (currentLayer->hasAnySelectedFrames()) { + emit selectionChanged(); + } + + mTimeLine->updateContent(); + } + else + { + if (frameNumber > 0) + { + PlaybackManager* playback = mEditor->playback(); + if (playback->isPlaying()) + { + playback->stop(); + } + if (playback->getSoundScrubActive()) + { + playback->playScrub(frameNumber); + } + mEditor->scrubTo(frameNumber); + + mTimeLine->scrubbing = true; + } + } + } +} + +void TimeLineTrackList::mouseMoveEvent(QMouseEvent* event) +{ + mMouseMoveX = event->pos().x(); + mFramePosMoveX = getFrameNumber(mMouseMoveX); + + if (primaryButton == Qt::MiddleButton) + { + mFrameOffset = qMin(qMax(0, mFrameLength - width() / getFrameSize()), qMax(0, mFrameOffset + mLastFrameNumber - mFramePosMoveX)); + update(); + emit offsetChanged(mFrameOffset); + } + else + { + if (mTimeLine->scrubbing) + { + if (mEditor->playback()->getSoundScrubActive() && mLastScrubFrame != mFramePosMoveX) + { + mEditor->playback()->playScrub(mFramePosMoveX); + mLastScrubFrame = mFramePosMoveX; + } + mEditor->scrubTo(mFramePosMoveX); + } + else + { + if (event->buttons() & Qt::LeftButton) { + if (mEditor->currentLayerIndex() < mEditor->layers()->count()) + { + Layer *currentLayer = mEditor->layers()->currentLayer(); + + // Check if the frame we clicked was selected + if (mCanMoveFrame) { + + // If it is the case, we move the selected frames in the layer + mMovingFrames = true; + } + else if (mCanBoxSelect) + { + // Otherwise, we do a box select + mBoxSelecting = true; + + currentLayer->deselectAll(); + currentLayer->setFrameSelected(mStartFrameNumber, true); + currentLayer->extendSelectionTo(mFramePosMoveX); + emit mEditor->selectedFramesChanged(); + } + mLastFrameNumber = mFramePosMoveX; + updateContent(); + } + } + update(); + } + } +} + +void TimeLineTrackList::mouseReleaseEvent(QMouseEvent* event) +{ + if (event->button() != primaryButton) return; + + int frameNumber = getFrameNumber(event->pos().x()); + if (frameNumber < 1) frameNumber = 1; + + if (mEditor->currentLayerIndex() != -1 && primaryButton != Qt::MiddleButton) + { + // We should affect the current layer based on what's selected, not where the mouse currently is. + Layer* currentLayer = mEditor->layers()->getLayer(mEditor->currentLayerIndex()); + Q_ASSERT(currentLayer); + + if (mMovingFrames) + { + int posUnderCursor = getFrameNumber(mMousePressX); + int offset = frameNumber - posUnderCursor; + + currentLayer->moveSelectedFrames(offset); + + mEditor->layers()->notifyAnimationLengthChanged(); + emit mEditor->framesModified(); + updateContent(); + } + else if (!mTimeLine->scrubbing && !mMovingFrames && !mClickSelecting && !mBoxSelecting) + { + // De-selecting if we didn't move, scrub nor select anything + bool multipleSelection = (event->modifiers() == Qt::ControlModifier); + + // Add/remove from already selected + currentLayer->toggleFrameSelected(frameNumber, multipleSelection); + emit mEditor->selectedFramesChanged(); + updateContent(); + } + } + + primaryButton = Qt::NoButton; + mTimeLine->scrubbing = false; + mMovingFrames = false; +} + +void TimeLineTrackList::mouseDoubleClickEvent(QMouseEvent* event) +{ + int frameNumber = getFrameNumber(event->pos().x()); + int layerNumber = getCellNumber(event->pos().y()); + + // -- layer -- + Layer* layer = mEditor->object()->getLayer(layerNumber); + if (layer && event->buttons() & Qt::LeftButton) + { + if ((layerNumber != -1) && (frameNumber > 0) && layerNumber < mEditor->object()->getLayerCount()) + { + if (!layer->keyExistsWhichCovers(frameNumber)) + { + mEditor->scrubTo(frameNumber); + } + + // The release event will toggle the frame on again, so we make sure it gets + // deselected now instead. + layer->setFrameSelected(frameNumber, true); + } + } + QWidget::mouseDoubleClickEvent(event); +} + +void TimeLineTrackList::hScrollChange(int x) +{ + mFrameOffset = x; + updateContent(); +} + +void TimeLineTrackList::vScrollChange(int x) +{ + updateContent(); +} + +void TimeLineTrackList::setCellDragY(const DragEvent& event, int y) +{ + mDragEvent = event; + mCellDragY = y; + updateContent(); +} + +bool TimeLineTrackList::trackScrubber() +{ + if (mPrevFrame == mEditor->currentFrame() && !mEditor->playback()->isPlaying()) + { + return false; + } + mPrevFrame = mEditor->currentFrame(); + + if (mEditor->currentFrame() <= mFrameOffset) + { + // Move the timeline back if the scrubber is offscreen to the left + mFrameOffset = mEditor->currentFrame() - 1; + emit offsetChanged(mFrameOffset); + return true; + } + else if (width() < (mEditor->currentFrame() - mFrameOffset + 1) * mFrameSize) + { + // Move timeline forward if the scrubber is offscreen to the right + if (mEditor->playback()->isPlaying()) + mFrameOffset = mFrameOffset + ((mEditor->currentFrame() - mFrameOffset) / 2); + else + mFrameOffset = mEditor->currentFrame() - width() / mFrameSize; + emit offsetChanged(mFrameOffset); + return true; + } + return false; +} + +void TimeLineTrackList::onDidLeaveWidget() +{ + // Reset last known frame pos to avoid wrong UI states when leaving the widget + mFramePosMoveX = 0; + update(); +} diff --git a/app/src/timelinecells.h b/app/src/timelinetracklist.h similarity index 70% rename from app/src/timelinecells.h rename to app/src/timelinetracklist.h index aa42d9ca32..b255eb05c0 100644 --- a/app/src/timelinecells.h +++ b/app/src/timelinetracklist.h @@ -15,12 +15,12 @@ GNU General Public License for more details. */ -#ifndef TIMELINECELLS_H -#define TIMELINECELLS_H +#ifndef TIMELINETRACKLIST_H +#define TIMELINETRACKLIST_H -#include #include -#include "layercamera.h" + +#include "pencildef.h" class Layer; enum class LayerVisibility; @@ -34,22 +34,14 @@ class QMenu; class QAction; enum class SETTING; -enum class TIMELINE_CELL_TYPE -{ - Layers, - Tracks -}; - -class TimeLineCells : public QWidget +class TimeLineTrackList : public QWidget { Q_OBJECT public: - TimeLineCells( TimeLine* parent, Editor* editor, TIMELINE_CELL_TYPE ); - ~TimeLineCells() override; + TimeLineTrackList( TimeLine* parent, Editor* editor); + ~TimeLineTrackList() override; - static int getOffsetX() { return mOffsetX; } - static int getOffsetY() { return mOffsetY; } int getLayerHeight() const { return mLayerHeight; } int getFrameLength() const { return mFrameLength; } @@ -59,8 +51,6 @@ class TimeLineCells : public QWidget void setFrameSize(int size); void clearCache() { delete mCache; mCache = nullptr; } - bool didDetachLayer() const; - void showCameraMenu(QPoint pos); signals: @@ -71,12 +61,12 @@ class TimeLineCells : public QWidget void insertNewKeyFrame(); public slots: + void onLayerCountChanged(int count); void updateContent(); void updateFrame(int frameNumber); void hScrollChange(int); void vScrollChange(int); - void onScrollingVerticallyStopped(); - void setMouseMoveY(int x); + void setCellDragY(const DragEvent& event, int y); protected: bool event(QEvent *event) override; @@ -91,9 +81,8 @@ private slots: void loadSetting(SETTING setting); private: - int getLayerNumber(int y) const; - int getInbetweenLayerNumber(int y) const; - int getLayerY(int layerNumber) const; + int getCellNumber(int y) const; + int getCellY(int layerNumber) const; int getFrameX(int frameNumber) const; int getFrameNumber(int x) const; @@ -101,60 +90,42 @@ private slots: bool trackScrubber(); void drawContent(); - void paintTicks(QPainter& painter, const QPalette& palette) const; - void paintOnionSkin(QPainter& painter) const; - void paintLayerGutter(QPainter& painter) const; void paintTrack(QPainter& painter, const Layer* layer, int x, int y, int width, int height, bool selected, int frameSize) const; void paintFrames(QPainter& painter, QColor trackCol, const Layer* layer, int y, int height, bool selected, int frameSize) const; void paintCurrentFrameBorder(QPainter& painter, int recLeft, int recTop, int recWidth, int recHeight) const; void paintFrameCursorOnCurrentLayer(QPainter& painter, int recTop, int recWidth, int recHeight) const; void paintSelectedFrames(QPainter& painter, const Layer* layer, const int layerIndex) const; - void paintLabel(QPainter& painter, const Layer* layer, int x, int y, int height, int width, bool selected, LayerVisibility layerVisibility) const; void paintSelection(QPainter& painter, int x, int y, int width, int height) const; void paintHighlightedFrame(QPainter& painter, int framePos, int recTop, int recWidth, int recHeight) const; - void editLayerProperties(Layer* layer) const; - void editLayerProperties(LayerCamera *layer) const; - void editLayerName(Layer* layer) const; - TimeLine* mTimeLine; Editor* mEditor; // the editor for which this timeLine operates PreferenceManager* mPrefs; - TIMELINE_CELL_TYPE mType; - QPixmap* mCache = nullptr; bool mRedrawContent = false; bool mDrawFrameNumber = true; bool mbShortScrub = false; int mFrameLength = 1; int mFrameSize = 0; - int mFontSize = 10; bool mScrubbing = false; bool mHighlightFrameEnabled = false; int mHighlightedFrame = -1; int mLayerHeight = 20; - int mStartY = 0; - int mEndY = 0; + + DragEvent mDragEvent = DragEvent::ENDED; int mCurrentLayerNumber = 0; int mLastScrubFrame = 0; - int mFromLayer = 0; - int mToLayer = 1; - int mStartLayerNumber = -1; int mStartFrameNumber = 0; int mLastFrameNumber = -1; - // is used to move layers, don't use this to get mousePos; - int mMouseMoveY = 0; + int mCellDragY = 0; int mPrevFrame = 0; int mFrameOffset = 0; - int mLayerOffset = 0; Qt::MouseButton primaryButton = Qt::NoButton; - bool mScrollingVertically = false; - bool mCanMoveFrame = false; bool mMovingFrames = false; @@ -168,11 +139,6 @@ private slots: int mMouseMoveX = 0; int mMousePressX = 0; - - const static int mOffsetX = 0; - const static int mOffsetY = 20; - const static int mLayerDetachThreshold = 5; - }; -#endif // TIMELINECELLS_H +#endif // TIMELINETRACKLIST_H diff --git a/core_lib/src/interface/editor.cpp b/core_lib/src/interface/editor.cpp index e2e5402e9f..f506f02978 100644 --- a/core_lib/src/interface/editor.cpp +++ b/core_lib/src/interface/editor.cpp @@ -578,8 +578,6 @@ void Editor::updateObject() { mObject->setActiveFramePoolSize(mPreferenceManager->getInt(SETTING::FRAME_POOL_SIZE)); } - - emit updateLayerCount(); } Status Editor::importBitmapImage(const QString& filePath, const QTransform& importTransform) @@ -992,28 +990,6 @@ void Editor::switchVisibilityOfLayer(int layerNumber) emit updateTimeLine(); } -void Editor::swapLayers(int i, int j) -{ - bool didSwapLayer = mObject->swapLayers(i, j); - if (!didSwapLayer) { return; } - - if (j < i) - { - layers()->setCurrentLayer(j + 1); - } - else - { - layers()->setCurrentLayer(j - 1); - } - emit updateTimeLine(); - mScribbleArea->onLayerChanged(); -} - -bool Editor::canSwapLayers(int layerIndexLeft, int layerIndexRight) const -{ - return mObject->canSwapLayers(layerIndexLeft, layerIndexRight); -} - void Editor::prepareSave() { for (auto mgr : mAllManagers) diff --git a/core_lib/src/interface/editor.h b/core_lib/src/interface/editor.h index 340433d9a7..b42006c527 100644 --- a/core_lib/src/interface/editor.h +++ b/core_lib/src/interface/editor.h @@ -149,7 +149,8 @@ class Editor : public QObject void updateTimeLine() const; void updateTimeLineCached(); - void updateLayerCount(); + + void updateBackup(); void objectLoaded(); @@ -201,8 +202,6 @@ class Editor : public QObject void removeKey(); void switchVisibilityOfLayer(int layerNumber); - void swapLayers(int i, int j); - bool canSwapLayers(int layerIndexLeft, int layerIndexRight) const; void backup(const QString& undoText); bool backup(int layerNumber, int frameNumber, const QString& undoText); diff --git a/core_lib/src/interface/scribblearea.cpp b/core_lib/src/interface/scribblearea.cpp index 647898e7d7..628aaca824 100644 --- a/core_lib/src/interface/scribblearea.cpp +++ b/core_lib/src/interface/scribblearea.cpp @@ -76,6 +76,8 @@ bool ScribbleArea::init() connect(mEditor->select(), &SelectionManager::selectionChanged, this, &ScribbleArea::onSelectionChanged); connect(mEditor->select(), &SelectionManager::needDeleteSelection, this, &ScribbleArea::deleteSelection); + connect(mEditor->layers(), &LayerManager::layerOrderChanged, this, &ScribbleArea::onLayerChanged); + connect(&mTiledBuffer, &TiledBuffer::tileUpdated, this, &ScribbleArea::onTileUpdated); connect(&mTiledBuffer, &TiledBuffer::tileCreated, this, &ScribbleArea::onTileCreated); diff --git a/core_lib/src/managers/layermanager.cpp b/core_lib/src/managers/layermanager.cpp index efc55f0910..fe4181e050 100644 --- a/core_lib/src/managers/layermanager.cpp +++ b/core_lib/src/managers/layermanager.cpp @@ -143,9 +143,7 @@ void LayerManager::gotoNextLayer() { if (editor()->currentLayerIndex() < object()->getLayerCount() - 1) { - currentLayer()->deselectAll(); - editor()->setCurrentLayerIndex(editor()->currentLayerIndex() + 1); - emit currentLayerChanged(editor()->currentLayerIndex()); + setCurrentLayer(editor()->currentLayerIndex() + 1); } } @@ -153,9 +151,7 @@ void LayerManager::gotoPreviouslayer() { if (editor()->currentLayerIndex() > 0) { - currentLayer()->deselectAll(); - editor()->setCurrentLayerIndex(editor()->currentLayerIndex() - 1); - emit currentLayerChanged(editor()->currentLayerIndex()); + setCurrentLayer(editor()->currentLayerIndex() - 1); } } @@ -310,6 +306,25 @@ int LayerManager::lastKeyFrameIndex() return maxPosition; } +bool LayerManager::swapLayers(int i, int j) +{ + bool didSwapLayer = object()->swapLayers(i, j); + if (!didSwapLayer) { return false; } + + if (j < i) + { + setCurrentLayer(j + 1); + } + else + { + setCurrentLayer(j - 1); + } + + emit layerOrderChanged(); + + return true; +} + int LayerManager::count() { return object()->getLayerCount(); diff --git a/core_lib/src/managers/layermanager.h b/core_lib/src/managers/layermanager.h index 254ebedb7d..04e3080ce5 100644 --- a/core_lib/src/managers/layermanager.h +++ b/core_lib/src/managers/layermanager.h @@ -59,6 +59,8 @@ class LayerManager : public BaseManager void gotoNextLayer(); void gotoPreviouslayer(); + bool swapLayers(int i, int j); + /** Returns a new Layer with the given LAYER_TYPE */ Layer* createLayer(Layer::LAYER_TYPE type, const QString& strLayerName); LayerBitmap* createBitmapLayer(const QString& strLayerName); @@ -85,6 +87,7 @@ class LayerManager : public BaseManager void layerCountChanged(int count); void animationLengthChanged(int length); void layerDeleted(int index); + void layerOrderChanged(); private: int getIndex(Layer*) const; diff --git a/core_lib/src/util/pencildef.h b/core_lib/src/util/pencildef.h index a16f6f89c6..820a188ece 100644 --- a/core_lib/src/util/pencildef.h +++ b/core_lib/src/util/pencildef.h @@ -105,6 +105,12 @@ enum class LayerVisibility // If you are adding new enum values here, be sure to update the ++/-- operators below }; +enum class DragEvent { + STARTED, + DRAGGING, + ENDED +}; + inline LayerVisibility& operator++(LayerVisibility& vis) { return vis = (vis == LayerVisibility::ALL) ? LayerVisibility::CURRENTONLY : static_cast(static_cast(vis)+1);