Skip to content

Commit

Permalink
Allow increasing and decreasing key frame exposure
Browse files Browse the repository at this point in the history
That is, moving all key frames to the right of a key frame to lengthen
or shorten the time that the target key frame is visible. This is
available either through the context menu, the Animation window menu or
with Ctrl + Shift + Plus/Minus.
  • Loading branch information
askmeaboutlo0m committed Aug 25, 2023
1 parent 4c1b067 commit 8f5cd1b
Show file tree
Hide file tree
Showing 4 changed files with 151 additions and 1 deletion.
1 change: 1 addition & 0 deletions ChangeLog
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ Unreleased Version 2.2.0-pre
* Fix: Don't mark guests as registered. Thanks to xxxx for reporting.
* Fix: Allow assigning a shortcut to open the Layouts dialog (F9 by default) and to the entries in the Help menu (nothing by default.)
* Fix: Make reloading the last brush preset slot-specific, since it's nonsense to clobber your current slot with the last preset you set in another one.
* Feature: Allow increasing and decreasing key frame exposure. Thanks Tabuley for suggesting.

2023-07-31 Version 2.2.0-beta.6
* Fix: Don't forget account password when entering a wrong session password.
Expand Down
7 changes: 6 additions & 1 deletion src/desktop/mainwindow.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3420,6 +3420,8 @@ void MainWindow::setupActions()
QAction *keyFramePaste = makeAction("key-frame-paste", tr("Paste Key Frame")).icon("edit-paste").noDefaultShortcut();
QAction *keyFrameProperties = makeAction("key-frame-properties", tr("Key Frame Properties...")).icon("configure").shortcut("Ctrl+Shift+P");
QAction *keyFrameDelete = makeAction("key-frame-delete", tr("Delete Key Frame")).icon("keyframe-remove").shortcut("Ctrl+Shift+G");
QAction *keyFrameExposureIncrease = makeAction("key-frame-exposure-increase", tr("Increase Key Frame Exposure")).icon("sidebar-expand-left").shortcut("Ctrl+Shift++");
QAction *keyFrameExposureDecrease = makeAction("key-frame-exposure-decrease", tr("Decrease Key Frame Exposure")).icon("sidebar-collapse-left").shortcut("Ctrl+Shift+-");
QAction *trackAdd = makeAction("track-add", tr("New Track")).icon("list-add").noDefaultShortcut();
QAction *trackVisible = makeAction("track-visible", tr("Track Visible")).checkable().noDefaultShortcut();
QAction *trackOnionSkin = makeAction("track-onion-skin", tr("Track Onion Skin")).checkable().shortcut("Ctrl+Shift+O");
Expand Down Expand Up @@ -3463,6 +3465,9 @@ void MainWindow::setupActions()
animationMenu->addAction(keyFrameProperties);
animationMenu->addAction(keyFrameDelete);
animationMenu->addSeparator();
animationMenu->addAction(keyFrameExposureIncrease);
animationMenu->addAction(keyFrameExposureDecrease);
animationMenu->addSeparator();
QMenu *animationLayerMenu = animationMenu->addMenu(
QIcon::fromTheme("layer-visible-on"), tr("Create Layer on Key Frame"));
animationLayerMenu->addAction(keyFrameCreateLayer);
Expand Down Expand Up @@ -3492,7 +3497,7 @@ void MainWindow::setupActions()

m_currentdoctools->addAction(showFlipbook);
m_dockLayers->setLayerEditActions({layerAdd, groupAdd, layerDupe, layerMerge, layerProperties, layerDelete, layerSetFillSource, keyFrameSetLayer, keyFrameCreateLayer, keyFrameCreateLayerNext, keyFrameCreateLayerPrev, keyFrameCreateGroup, keyFrameCreateGroupNext, keyFrameCreateGroupPrev, keyFrameDuplicateNext, keyFrameDuplicatePrev, layerKeyFrameGroup});
m_dockTimeline->setActions({keyFrameSetLayer, keyFrameSetEmpty, keyFrameCreateLayer, keyFrameCreateLayerNext, keyFrameCreateLayerPrev, keyFrameCreateGroup, keyFrameCreateGroupNext, keyFrameCreateGroupPrev, keyFrameDuplicateNext, keyFrameDuplicatePrev, keyFrameCut, keyFrameCopy, keyFramePaste, keyFrameProperties, keyFrameDelete, trackAdd, trackVisible, trackOnionSkin, trackDuplicate, trackRetitle, trackDelete, frameCountSet, framerateSet, frameNext, framePrev, trackAbove, trackBelow, animationLayerMenu, animationGroupMenu, animationDuplicateMenu}, layerViewNormal, layerViewCurrentFrame, showFlipbook);
m_dockTimeline->setActions({keyFrameSetLayer, keyFrameSetEmpty, keyFrameCreateLayer, keyFrameCreateLayerNext, keyFrameCreateLayerPrev, keyFrameCreateGroup, keyFrameCreateGroupNext, keyFrameCreateGroupPrev, keyFrameDuplicateNext, keyFrameDuplicatePrev, keyFrameCut, keyFrameCopy, keyFramePaste, keyFrameProperties, keyFrameDelete, keyFrameExposureIncrease, keyFrameExposureDecrease, trackAdd, trackVisible, trackOnionSkin, trackDuplicate, trackRetitle, trackDelete, frameCountSet, framerateSet, frameNext, framePrev, trackAbove, trackBelow, animationLayerMenu, animationGroupMenu, animationDuplicateMenu}, layerViewNormal, layerViewCurrentFrame, showFlipbook);

connect(showFlipbook, &QAction::triggered, this, &MainWindow::showFlipbook);

Expand Down
139 changes: 139 additions & 0 deletions src/desktop/widgets/timelinewidget.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ extern "C" {
#include <QPainter>
#include <QScrollBar>
#include <QSignalBlocker>
#include <algorithm>

namespace widgets {

Expand All @@ -42,6 +43,14 @@ struct TimelineWidget::Target {
TrackAction action;
};

struct Exposure {
int start;
int length;
bool hasFrameAtEnd;

bool isValid() const { return start != -1 && length != -1; }
};

struct TimelineWidget::Private {
canvas::CanvasModel *canvas = nullptr;

Expand Down Expand Up @@ -199,13 +208,71 @@ struct TimelineWidget::Private {
int frameIndex = keyFrame.frameIndex;
if(frameIndex <= currentFrame && frameIndex > bestFrameIndex) {
bestKeyFrame = &keyFrame;
bestFrameIndex = frameIndex;
}
}
return bestKeyFrame;
}
return nullptr;
}

Exposure currentExposure()
{
const canvas::TimelineTrack *track = trackById(currentTrackId);
if(!track) {
return {-1, -1, false};
}

const QVector<canvas::TimelineKeyFrame> &keyFrames = track->keyFrames;
int keyFrameCount = keyFrames.size();
if(keyFrameCount == 0) {
return {-1, -1, false};
}

int start = 0;
for(int i = 0; i < keyFrameCount; ++i) {
int frameIndex = keyFrames[i].frameIndex;
if(frameIndex <= currentFrame && frameIndex > start) {
start = frameIndex;
}
}

int invalidIndex = frameCount();
int lastIndex = invalidIndex;
for(int i = 0; i < keyFrameCount; ++i) {
int frameIndex = keyFrames[i].frameIndex;
if(frameIndex > start && frameIndex < lastIndex) {
lastIndex = frameIndex;
}
}
if(lastIndex == invalidIndex) {
return {-1, -1, false};
}

int length = lastIndex - start;
bool hasFrameAtEnd =
keyFrameBy(currentTrackId, invalidIndex - 1) != nullptr;
return {start, length, hasFrameAtEnd};
}

QVector<int> gatherCurrentExposureFrames(int start)
{
const canvas::TimelineTrack *track = trackById(currentTrackId);
QVector<int> frameIndexes;
if(track) {
const QVector<canvas::TimelineKeyFrame> &keyFrames =
track->keyFrames;
int keyFrameCount = keyFrames.size();
for(int i = 0; i < keyFrameCount; ++i) {
int frameIndex = keyFrames[i].frameIndex;
if(frameIndex > start) {
frameIndexes.append(frameIndex);
}
}
}
return frameIndexes;
}

QModelIndex layerIndexById(int layerId) const
{
if(canvas) {
Expand Down Expand Up @@ -349,6 +416,9 @@ void TimelineWidget::setActions(const Actions &actions)
d->frameMenu->addAction(actions.keyFrameProperties);
d->frameMenu->addAction(actions.keyFrameDelete);
d->frameMenu->addSeparator();
d->frameMenu->addAction(actions.keyFrameExposureIncrease);
d->frameMenu->addAction(actions.keyFrameExposureDecrease);
d->frameMenu->addSeparator();
d->frameMenu->addMenu(actions.animationLayerMenu);
d->frameMenu->addMenu(actions.animationGroupMenu);
d->frameMenu->addMenu(actions.animationDuplicateMenu);
Expand Down Expand Up @@ -391,6 +461,12 @@ void TimelineWidget::setActions(const Actions &actions)
connect(
actions.keyFrameDelete, &QAction::triggered, this,
&TimelineWidget::deleteKeyFrame);
connect(
actions.keyFrameExposureIncrease, &QAction::triggered, this,
&TimelineWidget::increaseKeyFrameExposure);
connect(
actions.keyFrameExposureDecrease, &QAction::triggered, this,
&TimelineWidget::decreaseKeyFrameExposure);
connect(
actions.trackAdd, &QAction::triggered, this, &TimelineWidget::addTrack);
connect(
Expand Down Expand Up @@ -1199,6 +1275,16 @@ void TimelineWidget::deleteKeyFrame()
}
}

void TimelineWidget::increaseKeyFrameExposure()
{
changeFrameExposure(1);
}

void TimelineWidget::decreaseKeyFrameExposure()
{
changeFrameExposure(-1);
}

void TimelineWidget::addTrack()
{
if(!d->editable) {
Expand Down Expand Up @@ -1513,6 +1599,47 @@ void TimelineWidget::setKeyFrameProperties(
}
}

void TimelineWidget::changeFrameExposure(int direction)
{
Q_ASSERT(direction == -1 || direction == 1);
if(!d->editable) {
return;
}

Exposure exposure = d->currentExposure();
if(!exposure.isValid()) {
return;
}

bool forward = direction == 1;
if(forward ? exposure.hasFrameAtEnd : exposure.length <= 1) {
return;
}

QVector<int> frameIndexes = d->gatherCurrentExposureFrames(exposure.start);
if(frameIndexes.isEmpty()) {
return;
}

// Order these correctly so we don't move frames over each other.
if(forward) {
std::sort(frameIndexes.rbegin(), frameIndexes.rend());
} else {
std::sort(frameIndexes.begin(), frameIndexes.end());
}

uint8_t contextId = d->canvas->localUserId();
QVector<drawdance::Message> messages;
messages.reserve(frameIndexes.size() + 1);
messages.append(drawdance::Message::makeUndoPoint(contextId));
for(int frameIndex : frameIndexes) {
messages.append(drawdance::Message::makeKeyFrameDelete(
contextId, d->currentTrackId, frameIndex, d->currentTrackId,
frameIndex + direction));
}
emit timelineEditCommands(messages.size(), messages.constData());
}

void TimelineWidget::updateActions()
{
if(!d->haveActions || !d->canvas) {
Expand Down Expand Up @@ -1604,6 +1731,18 @@ void TimelineWidget::updateActions()
d->actions.keyFrameProperties->setEnabled(keyFrameEditable);
d->actions.keyFrameDelete->setEnabled(keyFrameEditable);

bool canIncreaseExposure = false;
bool canDecreaseExposure = false;
if(timelineEditable && track) {
Exposure exposure = d->currentExposure();
if(exposure.isValid()) {
canIncreaseExposure = !exposure.hasFrameAtEnd;
canDecreaseExposure = exposure.length > 1;
}
}
d->actions.keyFrameExposureIncrease->setEnabled(canIncreaseExposure);
d->actions.keyFrameExposureDecrease->setEnabled(canDecreaseExposure);

updatePasteAction();
}

Expand Down
5 changes: 5 additions & 0 deletions src/desktop/widgets/timelinewidget.h
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@ class TimelineWidget final : public QWidget {
QAction *keyFramePaste = nullptr;
QAction *keyFrameProperties = nullptr;
QAction *keyFrameDelete = nullptr;
QAction *keyFrameExposureIncrease = nullptr;
QAction *keyFrameExposureDecrease = nullptr;
QAction *trackAdd = nullptr;
QAction *trackVisible = nullptr;
QAction *trackOnionSkin = nullptr;
Expand Down Expand Up @@ -112,6 +114,8 @@ private slots:
int trackId, int frame, const QString &title,
const QHash<int, bool> &layerVisibility);
void deleteKeyFrame();
void increaseKeyFrameExposure();
void decreaseKeyFrameExposure();
void addTrack();
void toggleTrackVisible(bool visible);
void toggleTrackOnionSkin(bool onionSkin);
Expand Down Expand Up @@ -145,6 +149,7 @@ private slots:
int trackId, int frame, const QString &prevTitle,
const QHash<int, bool> prevLayerVisibility, const QString &title,
const QHash<int, bool> layerVisibility);
void changeFrameExposure(int direction);
void updateActions();
void updateScrollbars();

Expand Down

0 comments on commit 8f5cd1b

Please sign in to comment.