Commit 7c639729 authored by Emmet O'Neill's avatar Emmet O'Neill Committed by Dmitry Kazakov

Animation Timeline Docker: Insert Keyframes with Timing.

Summary:
This patch adds timing functionality to the Animation Timeline Docker's "Insert N Keyframes" menu actions. Out of necessity, it also replaces the create-on-demand QInputDialog with a new TimelineInsertKeyframesDialog that was designed as a drop-in replacement that allows for getting more than a single variable of user input (i.e. frame count and timing) and which could easy be given more functionality in the future.

{F5845888}

The motivation behind this patch was to improve the "Insert N Keyframes Right/Left" action workflow by giving the animator control of frame timing. Previously, those actions worked by adding a number of immediately adjacent frames based on the user's desired number of frames to insert.

In animation, keyframes that are immediately adjacent to each other are described as being "on 1s", but other timings are also common - "on 2s", "on 3s", etc. - in which drawings are held for different amounts of time. Animations typically maintain a particular rhythm for a span of frames, but it's also very common for different parts of a single animation to switch timings.

This patch improves upon existing functionality to facilitate animating on 1s, 2s, 3s, and others. Now, a Krita user can specify a timing of 2 to quickly and easily insert keyframes "on 2s", a timing of 3 to insert "on 3s", and so on, allowing even longer timings if desired.

( Pair-programmed on a sunny Portland Saturday with @eoinoneill, of course! =] )

Test Plan:
1. Right-click a position on the animation Timeline docker.

2. Select "Keyframes > Insert N Keyframes Right" or "Keyframes > Insert N Keyframes Left".

3. A new dialog windows should pop up, asking for a number of frames and a timing.

4. Test different values as well as both the "Cancel" and "OK" buttons and check for predictable behavior in a variety of cases.

Reviewers: #krita, #krita_abyss, dkazakov, rempt, scottpetrovic, Bollebib

Subscribers: #krita_abyss, #krita, eoinoneill

Tags: #krita_abyss

Differential Revision: https://phabricator.kde.org/D12843
parent 6509644e
......@@ -20,6 +20,7 @@ set(KRITA_ANIMATIONDOCKER_SOURCES
timeline_node_list_keeper.cpp
timeline_color_scheme.cpp
timeline_insert_keyframe_dialog.cpp
kis_draggable_tool_button.cpp
kis_zoom_button.cpp
......
......@@ -794,9 +794,10 @@ bool TimelineFramesModel::copyFrame(const QModelIndex &dstIndex)
return m_d->addKeyframe(dstIndex.row(), dstIndex.column(), true);
}
bool TimelineFramesModel::insertFrames(int dstColumn, const QList<int> &dstRows, int count)
bool TimelineFramesModel::insertFrames(int dstColumn, const QList<int> &dstRows, int count, int timing)
{
if (dstRows.isEmpty() || count <= 0) return true;
timing = qMax(timing, 1);
KUndo2Command *parentCommand = new KUndo2Command(kundo2_i18np("Insert frame", "Insert %1 frames", count));
......@@ -811,10 +812,9 @@ bool TimelineFramesModel::insertFrames(int dstColumn, const QList<int> &dstRows,
}
}
setLastVisibleFrame(columnCount() + count - 1);
setLastVisibleFrame(columnCount() + (count * timing) - 1);
createOffsetFramesCommand(indexes, QPoint(count, 0), false, parentCommand);
createOffsetFramesCommand(indexes, QPoint((count * timing), 0), false, parentCommand);
Q_FOREACH (int row, dstRows) {
KisNodeDummy *dummy = m_d->converter->dummyFromRow(row);
......@@ -823,13 +823,13 @@ bool TimelineFramesModel::insertFrames(int dstColumn, const QList<int> &dstRows,
KisNodeSP node = dummy->node();
if (!KisAnimationUtils::supportsContentFrames(node)) continue;
for (int column = dstColumn; column < dstColumn + count; column++) {
for (int column = dstColumn; column < dstColumn + (count * timing); column += timing) {
KisAnimationUtils::createKeyframeCommand(m_d->image, node, KisKeyframeChannel::Content.id(), column, false, parentCommand);
}
}
const int oldTime = m_d->image->animationInterface()->currentUITime();
const int newTime = dstColumn > oldTime ? dstColumn : dstColumn + count - 1;
const int newTime = dstColumn > oldTime ? dstColumn : dstColumn + (count * timing) - 1;
new KisSwitchCurrentTimeCommand(m_d->image->animationInterface(),
oldTime,
......@@ -934,7 +934,6 @@ bool TimelineFramesModel::insertHoldFrames(QModelIndexList selectedIndexes, int
return true;
}
QString TimelineFramesModel::audioChannelFileName() const
{
return m_d->image ? m_d->image->animationInterface()->audioChannelFileName() : QString();
......
......@@ -59,7 +59,7 @@ public:
bool createFrame(const QModelIndex &dstIndex);
bool copyFrame(const QModelIndex &dstIndex);
bool insertFrames(int dstColumn, const QList<int> &dstRows, int count);
bool insertFrames(int dstColumn, const QList<int> &dstRows, int count, int timing = 1);
bool insertHoldFrames(QModelIndexList selectedIndexes, int count);
......
......@@ -22,6 +22,7 @@
#include "timeline_ruler_header.h"
#include "timeline_layers_header.h"
#include "timeline_insert_keyframe_dialog.h"
#include <cmath>
#include <limits>
......@@ -112,10 +113,11 @@ struct TimelineFramesView::Private
QAction *audioMuteAction;
KisSliderSpinBox *volumeSlider;
QMenu *layerEditingMenu;
QMenu *existingLayersMenu;
TimelineInsertKeyframeDialog *insertKeyframeDialog;
KisZoomButton *zoomDragButton;
bool dragInProgress;
......@@ -192,7 +194,6 @@ TimelineFramesView::TimelineFramesView(QWidget *parent)
connect(horizontalScrollBar(), SIGNAL(valueChanged(int)), SLOT(slotUpdateInfiniteFramesCount()));
connect(horizontalScrollBar(), SIGNAL(sliderReleased()), SLOT(slotUpdateInfiniteFramesCount()));
/********** New Layer Menu ***********************************************************/
m_d->addLayersButton = new QToolButton(this);
......@@ -270,6 +271,10 @@ TimelineFramesView::TimelineFramesView(QWidget *parent)
m_d->multiframeColorSelectorAction->setDefaultWidget(m_d->multiframeColorSelector);
connect(m_d->multiframeColorSelector, &KisColorLabelSelectorWidget::currentIndexChanged, this, &TimelineFramesView::slotColorLabelChanged);
/********** Insert Keyframes Dialog **************************************************/
m_d->insertKeyframeDialog = new TimelineInsertKeyframeDialog(this);
/********** Zoom Button **************************************************************/
m_d->zoomDragButton = new KisZoomButton(this);
......@@ -371,9 +376,6 @@ void TimelineFramesView::setActionManager( KisActionManager * actionManager)
action = m_d->actionMan->createAction("update_playback_range");
connect(action, SIGNAL(triggered()), SLOT(slotUpdatePlackbackRange()));
}
}
......@@ -563,9 +565,6 @@ void TimelineFramesView::slotAudioVolumeChanged(int value)
m_d->model->setAudioVolume(qreal(value) / 100.0);
}
void TimelineFramesView::slotUpdateInfiniteFramesCount()
{
if (horizontalScrollBar()->isSliderDown()) return;
......@@ -988,7 +987,6 @@ void TimelineFramesView::createFrameEditingMenuActions(QMenu *menu, bool addFram
KisActionManager::safePopulateMenu(menu, "add_duplicate_frame", m_d->actionMan);
menu->addSeparator();
}
}
void TimelineFramesView::mousePressEvent(QMouseEvent *event)
......@@ -1324,7 +1322,7 @@ void TimelineFramesView::calculateSelectionMetrics(int &minColumn, int &maxColum
}
}
void TimelineFramesView::insertFramesImpl(int insertAtColumn, int count, QSet<int> rows, bool forceEntireColumn)
void TimelineFramesView::insertFramesImpl(int insertAtColumn, int count, int timing, QSet<int> rows, bool forceEntireColumn)
{
if (forceEntireColumn) {
rows.clear();
......@@ -1335,11 +1333,11 @@ void TimelineFramesView::insertFramesImpl(int insertAtColumn, int count, QSet<in
}
if (!rows.isEmpty()) {
m_d->model->insertFrames(insertAtColumn, rows.toList(), count);
m_d->model->insertFrames(insertAtColumn, rows.toList(), count, timing);
}
}
void TimelineFramesView::slotInsertKeyframesLeft(int count, bool forceEntireColumn)
void TimelineFramesView::slotInsertKeyframesLeft(int count, int timing, bool forceEntireColumn)
{
QSet<int> rows;
int minColumn = 0;
......@@ -1351,10 +1349,10 @@ void TimelineFramesView::slotInsertKeyframesLeft(int count, bool forceEntireColu
count = qMax(1, maxColumn - minColumn + 1);
}
insertFramesImpl(minColumn, count, rows, forceEntireColumn);
insertFramesImpl(minColumn, count, timing, rows, forceEntireColumn);
}
void TimelineFramesView::slotInsertKeyframesRight(int count, bool forceEntireColumn)
void TimelineFramesView::slotInsertKeyframesRight(int count, int timing, bool forceEntireColumn)
{
QSet<int> rows;
int minColumn = 0;
......@@ -1366,78 +1364,52 @@ void TimelineFramesView::slotInsertKeyframesRight(int count, bool forceEntireCol
count = qMax(1, maxColumn - minColumn + 1);
}
insertFramesImpl(maxColumn + 1, count, rows, forceEntireColumn);
insertFramesImpl(maxColumn + 1, count, timing, rows, forceEntireColumn);
}
void TimelineFramesView::slotInsertColumnsLeft(int count)
void TimelineFramesView::slotInsertColumnsLeft(int count, int timing)
{
slotInsertKeyframesLeft(count, true);
slotInsertKeyframesLeft(count, timing, true);
}
void TimelineFramesView::slotInsertColumnsRight(int count)
void TimelineFramesView::slotInsertColumnsRight(int count, int timing)
{
slotInsertKeyframesRight(count, true);
slotInsertKeyframesRight(count, timing, true);
}
void TimelineFramesView::slotInsertKeyframesLeftCustom()
{
bool ok = false;
const int count = QInputDialog::getInt(this,
i18nc("@title:window", "Insert left"),
i18nc("@label:spinbox", "Enter number of frames"),
defaultNumberOfFramesToAdd(),
1, 10000, 1, &ok);
int count, timing;
if (ok) {
setDefaultNumberOfFramesToAdd(count);
slotInsertKeyframesLeft(count);
if (m_d->insertKeyframeDialog->promptUserSettings(count, timing)) {
slotInsertKeyframesLeft(count, timing, false);
}
}
void TimelineFramesView::slotInsertKeyframesRightCustom()
{
bool ok = false;
const int count = QInputDialog::getInt(this,
i18nc("@title:window", "Insert right"),
i18nc("@label:spinbox", "Enter number of frames"),
defaultNumberOfFramesToAdd(),
1, 10000, 1, &ok);
int count, timing;
if (ok) {
setDefaultNumberOfFramesToAdd(count);
slotInsertKeyframesRight(count);
if (m_d->insertKeyframeDialog->promptUserSettings(count, timing)) {
slotInsertKeyframesRight(count, timing, false);
}
}
void TimelineFramesView::slotInsertColumnsLeftCustom()
{
bool ok = false;
const int count = QInputDialog::getInt(this,
i18nc("@title:window", "Insert left"),
i18nc("@label:spinbox", "Enter number of columns"),
defaultNumberOfColumnsToAdd(),
1, 10000, 1, &ok);
int count, timing;
if (ok) {
setDefaultNumberOfColumnsToAdd(count);
slotInsertColumnsLeft(count);
if (m_d->insertKeyframeDialog->promptUserSettings(count, timing)) {
slotInsertColumnsLeft(count, timing);
}
}
void TimelineFramesView::slotInsertColumnsRightCustom()
{
bool ok = false;
const int count = QInputDialog::getInt(this,
i18nc("@title:window", "Insert right"),
i18nc("@label:spinbox", "Enter number of columns"),
defaultNumberOfColumnsToAdd(),
1, 10000, 1, &ok);
int count, timing;
if (ok) {
setDefaultNumberOfColumnsToAdd(count);
slotInsertColumnsRight(count);
if (m_d->insertKeyframeDialog->promptUserSettings(count, timing)) {
slotInsertColumnsRight(count, timing);
}
}
......
......@@ -27,7 +27,6 @@
class KisAction;
class TimelineWidget;
class KRITAANIMATIONDOCKER_EXPORT TimelineFramesView : public QTableView
{
Q_OBJECT
......@@ -55,20 +54,19 @@ private Q_SLOTS:
void slotSetEndTimeToCurrentPosition();
void slotUpdatePlackbackRange();
// Layer
void slotAddNewLayer();
void slotAddExistingLayer(QAction *action);
void slotDataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight);
void slotRemoveLayer();
void slotLayerContextMenuRequested(const QPoint &globalPos);
// New, Copy, Insert and Remove Frames
void slotNewFrame();
void slotCopyFrame();
void slotInsertKeyframesLeft(int count = -1, bool forceEntireColumn = false);
void slotInsertKeyframesRight(int count = -1, bool forceEntireColumn = false);
void slotInsertKeyframesLeft(int count = 1, int timing = 1, bool forceEntireColumn = false);
void slotInsertKeyframesRight(int count = 1, int timing = 1, bool forceEntireColumn = false);
void slotInsertKeyframesLeftCustom();
void slotInsertKeyframesRightCustom();
......@@ -76,10 +74,10 @@ private Q_SLOTS:
void slotRemoveFrame(bool forceEntireColumn = false, bool needsOffset = false);
void slotRemoveFramesAndShift(bool forceEntireColumn = false);
void slotInsertColumnsLeft(int count = -1);
void slotInsertColumnsLeft(int count = 1, int timing = 1);
void slotInsertColumnsLeftCustom();
void slotInsertColumnsRight(int count = -1);
void slotInsertColumnsRight(int count = 1, int timing = 1);
void slotInsertColumnsRightCustom();
void slotRemoveColumns();
......@@ -100,7 +98,7 @@ private Q_SLOTS:
void slotMirrorFrames(bool forceEntireColumn = false);
void slotMirrorColumns();
// Copy-paste
void slotCopyFrames(bool forceEntireColumn = false);
void slotCutFrames(bool forceEntireColumn = false);
void slotPasteFrames(bool forceEntireColumn = false);
......@@ -121,7 +119,7 @@ private Q_SLOTS:
void slotColorLabelChanged(int);
void slotEnsureRowVisible(int row);
// Audio
void slotSelectAudioChannelFile();
void slotAudioChannelMute(bool value);
void slotAudioChannelRemove();
......@@ -132,7 +130,7 @@ private:
void setFramesPerSecond(int fps);
void calculateSelectionMetrics(int &minColumn, int &maxColumn, QSet<int> &rows) const;
void insertFramesImpl(int insertAtColumn, int count, QSet<int> rows, bool forceEntireColumn);
void insertFramesImpl(int insertAtColumn, int count, int timing, QSet<int> rows, bool forceEntireColumn);
void createFrameEditingMenuActions(QMenu *menu, bool addFrameCreationActions);
......
#include "timeline_insert_keyframe_dialog.h"
#include <QLabel>
#include <QSpinBox>
#include <QDialogButtonBox>
#include <QVBoxLayout>
#include <QFormLayout>
#include <klocalizedstring.h>
TimelineInsertKeyframeDialog::TimelineInsertKeyframeDialog(QWidget *parent) :
QDialog(parent)
{
setWindowTitle(i18nc("@title:window","Insert Keyframes"));
setModal(true);
setLayout(new QVBoxLayout(this));
frameCountSpinbox.setMinimum(1);
frameCountSpinbox.setValue(1);
frameTimingSpinbox.setMinimum(1);
frameTimingSpinbox.setValue(1);
QWidget *formsWidget = new QWidget();
QFormLayout *formLayout = new QFormLayout();
formsWidget->setLayout( formLayout );
layout()->addWidget(formsWidget);
QDialogButtonBox *buttonbox = new QDialogButtonBox(QDialogButtonBox::Cancel | QDialogButtonBox::Ok);
layout()->addWidget(buttonbox);
formLayout->addRow(QString(i18nc("@label:spinbox", "Number of frames:")), &frameCountSpinbox);
formLayout->addRow(QString(i18nc("@label:spinbox", "Frame timing:")), &frameTimingSpinbox);
connect(buttonbox, SIGNAL(accepted()), this, SLOT(accept()));
connect(buttonbox, SIGNAL(rejected()), this, SLOT(reject()));
}
bool TimelineInsertKeyframeDialog::promptUserSettings(int &out_count, int &out_timing){
if(exec() == QDialog::Accepted){
out_count = frameCountSpinbox.value();
out_timing = frameTimingSpinbox.value();
return true;
}
return false;
}
#ifndef __TIMELINE_INSERT_KEYFRAME_DIALOG_H
#define __TIMELINE_INSERT_KEYFRAME_DIALOG_H
#include "kritaanimationdocker_export.h"
#include <QDialog>
#include <QSpinBox>
class KRITAANIMATIONDOCKER_EXPORT TimelineInsertKeyframeDialog : QDialog {
Q_OBJECT
private:
QSpinBox frameCountSpinbox;
QSpinBox frameTimingSpinbox;
public:
TimelineInsertKeyframeDialog(QWidget *parent = 0);
bool promptUserSettings(int &count, int &timing);
};
#endif // __TIMELINE_INSERT_KEYFRAME_DIALOG_H
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment