Enhancement of the Shift and Trace Feature (#2212)

* shift and trace enhancement

* override shortcuts
This commit is contained in:
shun-iwasawa 2018-08-07 16:22:49 +09:00 committed by masafumi-inoue
parent a18e658d3e
commit 2b24e83965
17 changed files with 909 additions and 182 deletions

View file

@ -118,7 +118,9 @@ enum class PredefinedRect {
ZOOM_OUT_AREA,
ZOOM_OUT,
LAYER_FOOTER_PANEL,
PREVIEW_FRAME_AREA
PREVIEW_FRAME_AREA,
SHIFTTRACE_DOT,
SHIFTTRACE_DOT_AREA
};
enum class PredefinedLine {
LOCKED, //! dotted vertical line when cell is locked

View file

@ -669,6 +669,26 @@ protected slots:
void updateRealTimePickLabel(const int, const int, const int);
};
//=============================================================================
//
// ShiftTraceToolOptionBox
// shown only when "Edit Shift" mode is active
//
//=============================================================================
class ShiftTraceToolOptionBox final : public ToolOptionsBox {
Q_OBJECT
QPushButton *m_resetPrevGhostBtn;
QPushButton *m_resetAfterGhostBtn;
void resetGhost(int index);
public:
ShiftTraceToolOptionBox(QWidget *parent = 0);
protected slots:
void onResetPrevGhostBtnPressed();
void onResetAfterGhostBtnPressed();
};
//-----------------------------------------------------------------------------
class DVAPI ToolOptions final : public QFrame {

View file

@ -7,6 +7,8 @@
#include "tcommon.h"
#include "tgeometry.h"
#include <QList>
#undef DVAPI
#undef DVVAR
#ifdef TOONZLIB_EXPORTS
@ -114,6 +116,23 @@ since underlying onion-skinned drawings must be visible.
}
void setShiftTraceGhostCenter(int index, const TPointD &center);
const int getShiftTraceGhostFrameOffset(int index) {
return m_ghostFrame[index];
}
void setShiftTraceGhostFrameOffset(int index, int offset) {
m_ghostFrame[index] = offset;
}
const int getGhostFlipKey() {
return (m_ghostFlipKeys.isEmpty()) ? 0 : m_ghostFlipKeys.last();
}
void appendGhostFlipKey(int key) {
m_ghostFlipKeys.removeAll(key);
m_ghostFlipKeys.append(key);
}
void removeGhostFlipKey(int key) { m_ghostFlipKeys.removeAll(key); }
void clearGhostFlipKey() { m_ghostFlipKeys.clear(); }
private:
std::vector<int> m_fos, m_mos; //!< Fixed and Mobile Onion Skin indices
bool m_enabled; //!< Whether onion skin is enabled
@ -122,6 +141,9 @@ private:
ShiftTraceStatus m_shiftTraceStatus;
TAffine m_ghostAff[2];
TPointD m_ghostCenter[2];
int m_ghostFrame[2]; // relative frame position of the ghosts
QList<int> m_ghostFlipKeys; // If F1, F2 or F3 key is pressed, then only
// display the corresponding ghost
};
//***************************************************************************

View file

@ -36,6 +36,7 @@
#include "toonz/preferences.h"
#include "toonz/tstageobjecttree.h"
#include "toonz/mypaintbrushstyle.h"
#include "toonz/tonionskinmaskhandle.h"
// TnzCore includes
#include "tproperty.h"
@ -2496,6 +2497,45 @@ void StylePickerToolOptionsBox::updateRealTimePickLabel(const int ink,
}
}
//=============================================================================
// ShiftTraceToolOptionBox
//-----------------------------------------------------------------------------
ShiftTraceToolOptionBox::ShiftTraceToolOptionBox(QWidget *parent)
: ToolOptionsBox(parent) {
setFrameStyle(QFrame::StyledPanel);
setFixedHeight(26);
m_resetPrevGhostBtn =
new QPushButton(tr("Reset Shift of Previous Drawing"), this);
m_resetAfterGhostBtn =
new QPushButton(tr("Reset Shift of Forward Drawing"), this);
m_layout->addWidget(m_resetPrevGhostBtn, 0);
m_layout->addWidget(m_resetAfterGhostBtn, 0);
m_layout->addStretch(1);
connect(m_resetPrevGhostBtn, SIGNAL(clicked()), this,
SLOT(onResetPrevGhostBtnPressed()));
connect(m_resetAfterGhostBtn, SIGNAL(clicked()), this,
SLOT(onResetAfterGhostBtnPressed()));
}
void ShiftTraceToolOptionBox::resetGhost(int index) {
TTool::Application *app = TTool::getApplication();
OnionSkinMask osm = app->getCurrentOnionSkin()->getOnionSkinMask();
osm.setShiftTraceGhostCenter(index, TPointD());
osm.setShiftTraceGhostAff(index, TAffine());
app->getCurrentOnionSkin()->setOnionSkinMask(osm);
app->getCurrentOnionSkin()->notifyOnionSkinMaskChanged();
TTool *tool = app->getCurrentTool()->getTool();
if (tool) tool->reset();
}
void ShiftTraceToolOptionBox::onResetPrevGhostBtnPressed() { resetGhost(0); }
void ShiftTraceToolOptionBox::onResetAfterGhostBtnPressed() { resetGhost(1); }
//=============================================================================
// ToolOptions
//-----------------------------------------------------------------------------
@ -2604,6 +2644,8 @@ void ToolOptions::onToolSwitched() {
} else if (tool->getName() == T_StylePicker)
panel = new StylePickerToolOptionsBox(0, tool, currPalette, currTool,
app->getPaletteController());
else if (tool->getName() == "T_ShiftTrace")
panel = new ShiftTraceToolOptionBox(this);
else
panel = tool->createOptionsBox(); // Only this line should remain out
// of that if/else monstrosity

View file

@ -54,7 +54,9 @@ FrameHeadGadget::~FrameHeadGadget() {}
void FrameHeadGadget::draw(QPainter &p, const QColor &lightColor,
const QColor &darkColor) {
// drawPlayingHead(p, lightColor, darkColor);
if (!Preferences::instance()->isOnionSkinEnabled()) return;
if (CommandManager::instance()->getAction(MI_ShiftTrace)->isChecked())
drawShiftTraceMarker(p);
else if (Preferences::instance()->isOnionSkinEnabled())
drawOnionSkinSelection(p, lightColor, darkColor);
}
@ -294,7 +296,8 @@ void FrameHeadGadget::setMos(int frame, bool on) {
FilmstripFrameHeadGadget::FilmstripFrameHeadGadget(FilmstripFrames *filmstrip)
: m_filmstrip(filmstrip)
, m_dy(m_filmstrip->getIconSize().height() + fs_frameSpacing +
fs_iconMarginTop + fs_iconMarginBottom) {}
fs_iconMarginTop + fs_iconMarginBottom)
, m_highlightedghostFrame(-1) {}
int FilmstripFrameHeadGadget::getY() const { return 50; }
@ -442,7 +445,83 @@ void FilmstripFrameHeadGadget::drawOnionSkinSelection(QPainter &p,
//-----------------------------------------------------------------------------
void FilmstripFrameHeadGadget::drawShiftTraceMarker(QPainter &p) {
int currentRow = getCurrentFrame();
TPixel frontPixel, backPixel;
bool inksOnly;
Preferences::instance()->getOnionData(frontPixel, backPixel, inksOnly);
QColor frontColor((int)frontPixel.r, (int)frontPixel.g, (int)frontPixel.b);
QColor backColor((int)backPixel.r, (int)backPixel.g, (int)backPixel.b);
OnionSkinMask osMask =
TApp::instance()->getCurrentOnionSkin()->getOnionSkinMask();
// draw lines to ghost frames
int prevOffset = osMask.getShiftTraceGhostFrameOffset(0);
int forwardOffset = osMask.getShiftTraceGhostFrameOffset(1);
const int shiftTraceDotSize = 12;
const int shiftTraceDotXOffset = 3;
int dotYPos = (m_dy - shiftTraceDotSize) / 2;
if (currentRow > 0 && prevOffset < 0) // previous ghost
{
p.setPen(backColor);
int y0 = index2y(currentRow + prevOffset) + dotYPos + shiftTraceDotSize;
int y1 = index2y(currentRow);
p.drawLine(shiftTraceDotXOffset + shiftTraceDotSize / 2, y0,
shiftTraceDotXOffset + shiftTraceDotSize / 2, y1);
}
if (forwardOffset > 0) // forward ghost
{
p.setPen(frontColor);
int y0 = index2y(currentRow + 1);
int y1 = index2y(currentRow + forwardOffset) + dotYPos + shiftTraceDotSize;
p.drawLine(shiftTraceDotXOffset + shiftTraceDotSize / 2, y0,
shiftTraceDotXOffset + shiftTraceDotSize / 2, y1);
}
// draw dots
std::vector<int> offsVec = {prevOffset, 0, forwardOffset};
std::vector<QColor> colorsVec = {backColor, QColor(0, 162, 232), frontColor};
QFont currentFont = p.font();
QFont tmpFont = p.font();
tmpFont.setPointSize(7);
p.setFont(tmpFont);
for (int i = 0; i < 3; i++) {
if (i != 1 && offsVec[i] == 0) continue;
p.setPen(colorsVec[i]);
p.setBrush(Qt::gray);
int topPos = index2y(currentRow + offsVec[i]) + dotYPos;
QRect dotRect(shiftTraceDotXOffset, topPos, shiftTraceDotSize,
shiftTraceDotSize);
p.drawRect(dotRect);
// draw shortcut numbers
p.setPen(Qt::black);
p.drawText(dotRect, Qt::AlignCenter, QString::number(i + 1));
}
p.setFont(currentFont);
// highlight on mouse over
if (m_highlightedghostFrame >= 0) {
p.setPen(QColor(255, 255, 0));
p.setBrush(QColor(255, 255, 0, 180));
int topPos = index2y(m_highlightedghostFrame) + dotYPos;
QRect dotRect(shiftTraceDotXOffset, topPos, shiftTraceDotSize,
shiftTraceDotSize);
p.drawRect(dotRect);
}
}
//-----------------------------------------------------------------------------
bool FilmstripFrameHeadGadget::eventFilter(QObject *obj, QEvent *e) {
// shift & trace case
if (CommandManager::instance()->getAction(MI_ShiftTrace)->isChecked())
return shiftTraceEventFilter(obj, e);
if (!Preferences::instance()->isOnionSkinEnabled()) return false;
QWidget *viewer = dynamic_cast<QWidget *>(obj);
@ -650,6 +729,97 @@ bool FilmstripFrameHeadGadget::eventFilter(QObject *obj, QEvent *e) {
//-----------------------------------------------------------------------------
bool FilmstripFrameHeadGadget::shiftTraceEventFilter(QObject *obj, QEvent *e) {
QWidget *viewer = dynamic_cast<QWidget *>(obj);
if (e->type() != QEvent::MouseButtonPress && e->type() != QEvent::MouseMove)
return false;
const int shiftTraceDotSize = 12;
const int shiftTraceDotXOffset = 3;
int dotYPos = (m_dy - shiftTraceDotSize) / 2;
QRect dotRect(shiftTraceDotXOffset, dotYPos, shiftTraceDotSize,
shiftTraceDotSize);
// reset highlight
if (m_highlightedghostFrame >= 0) {
m_highlightedghostFrame = -1;
viewer->update();
}
QMouseEvent *mouseEvent = dynamic_cast<QMouseEvent *>(e);
int frame = y2index(mouseEvent->pos().y());
// position from top-left of the frame
QPoint mousePos = mouseEvent->pos() + QPoint(0, -index2y(frame));
int currentFrame = getCurrentFrame();
if (mousePos.x() < dotRect.left() || mousePos.x() > dotRect.right())
return false;
if (e->type() == QEvent::MouseButtonPress) {
if (frame == currentFrame) {
if (dotRect.contains(mousePos))
OnioniSkinMaskGUI::resetShiftTraceFrameOffset();
else
return false;
} else {
OnionSkinMask osMask =
TApp::instance()->getCurrentOnionSkin()->getOnionSkinMask();
int prevOffset = osMask.getShiftTraceGhostFrameOffset(0);
int forwardOffset = osMask.getShiftTraceGhostFrameOffset(1);
// Hide previous ghost
if (frame == currentFrame + prevOffset)
osMask.setShiftTraceGhostFrameOffset(0, 0);
// Hide forward ghost
else if (frame == currentFrame + forwardOffset)
osMask.setShiftTraceGhostFrameOffset(1, 0);
// Move previous ghost
else if (frame < currentFrame)
osMask.setShiftTraceGhostFrameOffset(0, frame - currentFrame);
// Move forward ghost
else
osMask.setShiftTraceGhostFrameOffset(1, frame - currentFrame);
TApp::instance()->getCurrentOnionSkin()->setOnionSkinMask(osMask);
}
TApp::instance()->getCurrentOnionSkin()->notifyOnionSkinMaskChanged();
return true;
}
//----
else if (e->type() == QEvent::MouseMove) {
if (frame == currentFrame) {
if (dotRect.contains(mousePos)) {
m_highlightedghostFrame = frame;
viewer->setToolTip(
tr("Click to Reset Shift & Trace Markers to Neighbor Frames\nHold "
"F2 Key on the Viewer to Show This Frame Only"));
} else {
viewer->setToolTip(tr(""));
return false;
}
} else {
m_highlightedghostFrame = frame;
OnionSkinMask osMask =
TApp::instance()->getCurrentOnionSkin()->getOnionSkinMask();
int prevOffset = osMask.getShiftTraceGhostFrameOffset(0);
int forwardOffset = osMask.getShiftTraceGhostFrameOffset(1);
// Hide ghost
if (frame == currentFrame + prevOffset)
viewer->setToolTip(
tr("Click to Hide This Frame from Shift & Trace\nHold F1 Key on "
"the Viewer to Show This Frame Only"));
else if (frame == currentFrame + forwardOffset)
viewer->setToolTip(
tr("Click to Hide This Frame from Shift & Trace\nHold F3 Key on "
"the Viewer to Show This Frame Only"));
// Move ghost
else
viewer->setToolTip(tr("Click to Move Shift & Trace Marker"));
}
}
return true;
}
//-----------------------------------------------------------------------------
class OnionSkinToggle final : public MenuItemHandler {
public:
OnionSkinToggle() : MenuItemHandler(MI_OnionSkin) {}

View file

@ -46,6 +46,8 @@ public:
virtual void drawOnionSkinSelection(QPainter &p, const QColor &lightColor,
const QColor &darkColor);
virtual void drawShiftTraceMarker(QPainter &p) {}
virtual int getY() const = 0;
virtual int index2y(int index) const = 0;
@ -70,6 +72,7 @@ public:
class FilmstripFrameHeadGadget final : public FrameHeadGadget {
FilmstripFrames *m_filmstrip;
int m_dy;
int m_highlightedghostFrame;
public:
FilmstripFrameHeadGadget(FilmstripFrames *filmstrip);
@ -81,10 +84,13 @@ public:
void drawOnionSkinSelection(QPainter &p, const QColor &lightColor,
const QColor &darkColor) override;
void drawShiftTraceMarker(QPainter &p) override;
void setCurrentFrame(int index) const override;
int getCurrentFrame() const override;
bool eventFilter(QObject *obj, QEvent *event) override;
bool shiftTraceEventFilter(QObject *obj, QEvent *event);
};
#endif

View file

@ -1960,6 +1960,8 @@ void MainWindow::defineActions() {
MenuViewCommandType);
createToggle(MI_EditShift, tr("Edit Shift"), "", false, MenuViewCommandType);
createToggle(MI_NoShift, tr("No Shift"), "", false, MenuViewCommandType);
CommandManager::instance()->enable(MI_EditShift, false);
CommandManager::instance()->enable(MI_NoShift, false);
createAction(MI_ResetShift, tr("Reset Shift"), "", MenuViewCommandType);
if (QGLPixelBuffer::hasOpenGLPbuffers())

View file

@ -3,6 +3,11 @@
#include "onionskinmaskgui.h"
#include "tapp.h"
#include "toonz/tonionskinmaskhandle.h"
#include "toonz/tframehandle.h"
#include "toonz/txshlevelhandle.h"
#include "toonz/txsheethandle.h"
#include "toonz/tcolumnhandle.h"
#include "toonz/txshsimplelevel.h"
#include "toonz/onionskinmask.h"
@ -105,3 +110,66 @@ void OnioniSkinMaskGUI::addOnionSkinCommand(QMenu *menu, bool isFilmStrip) {
SLOT(activate()));
}
}
//------------------------------------------------------------------------------
void OnioniSkinMaskGUI::resetShiftTraceFrameOffset() {
auto setGhostOffset = [](int firstOffset, int secondOffset) {
OnionSkinMask osm =
TApp::instance()->getCurrentOnionSkin()->getOnionSkinMask();
osm.setShiftTraceGhostFrameOffset(0, firstOffset);
osm.setShiftTraceGhostFrameOffset(1, secondOffset);
TApp::instance()->getCurrentOnionSkin()->setOnionSkinMask(osm);
};
TApp *app = TApp::instance();
if (app->getCurrentFrame()->isEditingLevel()) {
TXshSimpleLevel *level = app->getCurrentLevel()->getSimpleLevel();
if (!level) {
setGhostOffset(0, 0);
return;
}
TFrameId fid = app->getCurrentFrame()->getFid();
int firstOffset = (fid > level->getFirstFid()) ? -1 : 0;
int secondOffset = (fid < level->getLastFid()) ? 1 : 0;
setGhostOffset(firstOffset, secondOffset);
} else { // when scene frame is selected
TXsheet *xsh = app->getCurrentXsheet()->getXsheet();
int col = app->getCurrentColumn()->getColumnIndex();
TXshColumn *column = xsh->getColumn(col);
if (!column || column->isEmpty()) {
setGhostOffset(0, 0);
return;
}
int r0, r1;
column->getRange(r0, r1);
int row = app->getCurrentFrame()->getFrame();
TXshCell cell = xsh->getCell(row, col);
int firstOffset = -1;
while (1) {
int r = row + firstOffset;
if (r < r0) {
firstOffset = 0;
break;
}
if (!xsh->getCell(r, col).isEmpty() && xsh->getCell(r, col) != cell) {
break;
}
firstOffset--;
}
int secondOffset = 1;
while (1) {
int r = row + secondOffset;
if (r > r1) {
secondOffset = 0;
break;
}
if (!xsh->getCell(r, col).isEmpty() && xsh->getCell(r, col) != cell) {
break;
}
secondOffset++;
}
setGhostOffset(firstOffset, secondOffset);
}
}

View file

@ -15,6 +15,8 @@ namespace OnioniSkinMaskGUI {
// Da fare per la filmstrip!!
void addOnionSkinCommand(QMenu *, bool isFilmStrip = false);
void resetShiftTraceFrameOffset();
//=============================================================================
// OnionSkinSwitcher
//-----------------------------------------------------------------------------

View file

@ -391,10 +391,9 @@ public:
if (std::string(m_cmdId) == MI_ShiftTrace) {
cm->enable(MI_EditShift, checked);
cm->enable(MI_NoShift, checked);
if (!checked) {
cm->setChecked(MI_EditShift, false);
}
if (checked) OnioniSkinMaskGUI::resetShiftTraceFrameOffset();
// cm->getAction(MI_NoShift)->setChecked(false);
TApp::instance()->getCurrentOnionSkin()->notifyOnionSkinMaskChanged();
} else if (std::string(m_cmdId) == MI_EditShift) {
if (checked) {
QAction *noShiftAction =
@ -427,6 +426,7 @@ public:
OnionSkinMask osm =
TApp::instance()->getCurrentOnionSkin()->getOnionSkinMask();
osm.setShiftTraceStatus(status);
osm.clearGhostFlipKey();
TApp::instance()->getCurrentXsheet()->notifyXsheetChanged();
TApp::instance()->getCurrentOnionSkin()->setOnionSkinMask(osm);
}
@ -2379,16 +2379,22 @@ int SceneViewer::posToRow(const TPointD &p, double distance,
bool includeInvisible) const {
int oldRasterizePli = TXshSimpleLevel::m_rasterizePli;
TApp *app = TApp::instance();
ToonzScene *scene = app->getCurrentScene()->getScene();
TXsheet *xsh = app->getCurrentXsheet()->getXsheet();
int frame = app->getCurrentFrame()->getFrame();
int currentColumnIndex = app->getCurrentColumn()->getColumnIndex();
OnionSkinMask osm = app->getCurrentOnionSkin()->getOnionSkinMask();
TPointD pos = TPointD(p.x - width() / 2, p.y - height() / 2);
Stage::Picker picker(getViewMatrix(), pos, m_visualSettings);
picker.setDistance(distance);
if (app->getCurrentFrame()->isEditingLevel()) {
Stage::visit(picker, app->getCurrentLevel()->getLevel(),
app->getCurrentFrame()->getFid(), osm,
app->getCurrentFrame()->isPlaying(), false);
} else {
ToonzScene *scene = app->getCurrentScene()->getScene();
TXsheet *xsh = app->getCurrentXsheet()->getXsheet();
int frame = app->getCurrentFrame()->getFrame();
int currentColumnIndex = app->getCurrentColumn()->getColumnIndex();
TXshSimpleLevel::m_rasterizePli = 0;
Stage::VisitArgs args;
@ -2400,7 +2406,7 @@ int SceneViewer::posToRow(const TPointD &p, double distance,
args.m_onlyVisible = includeInvisible;
Stage::visit(picker, args);
}
TXshSimpleLevel::m_rasterizePli = oldRasterizePli;
return picker.getRow();
}

View file

@ -1039,6 +1039,16 @@ bool SceneViewer::event(QEvent *e) {
else if (tool && tool->isEventAcceptable(e)) {
e->accept();
}
// if the Shift & Trace mode is active, then override F1, F2 and F3 key
// actions by flipping feature
else if (CommandManager::instance()
->getAction(MI_ShiftTrace)
->isChecked() &&
TTool::getTool("T_ShiftTrace", TTool::ToonzImage)
->isEventAcceptable(e)) {
e->accept();
}
return true;
}
if (e->type() == QEvent::KeyRelease) {
@ -1211,6 +1221,18 @@ void SceneViewer::keyPressEvent(QKeyEvent *event) {
event->ignore();
return true;
}
// pressing F1, F2 or F3 key will flip between corresponding ghost
if (CommandManager::instance()->getAction(MI_ShiftTrace)->isChecked() &&
(Qt::Key_F1 <= key && key <= Qt::Key_F3)) {
OnionSkinMask osm =
TApp::instance()->getCurrentOnionSkin()->getOnionSkinMask();
if (osm.getGhostFlipKey() != key) {
osm.appendGhostFlipKey(key);
TApp::instance()->getCurrentOnionSkin()->setOnionSkinMask(osm);
TApp::instance()->getCurrentOnionSkin()->notifyOnionSkinMaskChanged();
}
return true;
}
}
if (!tool->isEnabled()) return false;
@ -1309,6 +1331,15 @@ void SceneViewer::keyReleaseEvent(QKeyEvent *event) {
setToolCursor(this, tool->getCursorId());
}
if (CommandManager::instance()->getAction(MI_ShiftTrace)->isChecked() &&
(Qt::Key_F1 <= key && key <= Qt::Key_F3)) {
OnionSkinMask osm =
TApp::instance()->getCurrentOnionSkin()->getOnionSkinMask();
osm.removeGhostFlipKey(key);
TApp::instance()->getCurrentOnionSkin()->setOnionSkinMask(osm);
TApp::instance()->getCurrentOnionSkin()->notifyOnionSkinMaskChanged();
}
if (tool->getName() == T_Type)
event->accept();
else

View file

@ -14,13 +14,20 @@
#include "toonz/txsheethandle.h"
#include "toonz/tframehandle.h"
#include "toonz/tcolumnhandle.h"
#include "toonz/txshlevelhandle.h"
#include "toonz/txshsimplelevel.h"
#include "toonz/dpiscale.h"
#include "toonz/stage.h"
#include "tapp.h"
#include "tpixel.h"
#include "toonzqt/menubarcommand.h"
#include "toonz/preferences.h"
#include "toonzqt/gutil.h"
#include "tgl.h"
#include <math.h>
#include <QKeyEvent>
//=============================================================================
@ -54,12 +61,14 @@ public:
enum GadgetId {
NoGadget,
NoGadget_InBox,
CurveP0Gadget,
CurveP1Gadget,
CurvePmGadget,
MoveCenterGadget,
RotateGadget,
TranslateGadget
TranslateGadget,
ScaleGadget
};
inline bool isCurveGadget(GadgetId id) const {
return CurveP0Gadget <= id && id <= CurvePmGadget;
@ -80,6 +89,8 @@ private:
TAffine m_aff[2];
TPointD m_center[2];
TAffine m_oldAff;
public:
ShiftTraceTool();
@ -92,8 +103,10 @@ public:
void updateGhost();
void reset() override {
int ghostIndex = m_ghostIndex;
onActivate();
invalidate();
m_ghostIndex = ghostIndex;
}
void mouseMove(const TPointD &, const TMouseEvent &e) override;
@ -124,6 +137,7 @@ public:
QAction *action = CommandManager::instance()->getAction("MI_EditShift");
action->setChecked(false);
}
bool isEventAcceptable(QEvent *e) override;
int getCursorId() const override;
};
@ -152,16 +166,35 @@ void ShiftTraceTool::clearData() {
}
void ShiftTraceTool::updateBox() {
if (0 <= m_ghostIndex && m_ghostIndex < 2 && m_row[m_ghostIndex] >= 0) {
int col = TApp::instance()->getCurrentColumn()->getColumnIndex();
if (m_ghostIndex < 0 || 2 <= m_ghostIndex || m_row[m_ghostIndex] < 0) return;
TImageP img;
TApp *app = TApp::instance();
if (app->getCurrentFrame()->isEditingScene()) {
int col = app->getCurrentColumn()->getColumnIndex();
int row = m_row[m_ghostIndex];
TXsheet *xsh = TApp::instance()->getCurrentXsheet()->getXsheet();
TXsheet *xsh = app->getCurrentXsheet()->getXsheet();
TXshCell cell = xsh->getCell(row, col);
TXshSimpleLevel *sl = cell.getSimpleLevel();
if (sl) {
m_dpiAff = getDpiAffine(sl, cell.m_frameId);
TImageP img = cell.getImage(false);
img = cell.getImage(false);
}
}
// on editing level
else {
TXshLevel *level = app->getCurrentLevel()->getLevel();
if (!level) return;
TXshSimpleLevel *sl = level->getSimpleLevel();
if (!sl) return;
const TFrameId &ghostFid = sl->index2fid(m_row[m_ghostIndex]);
m_dpiAff = getDpiAffine(sl, ghostFid);
img = sl->getFrame(ghostFid, false);
}
if (img) {
if (TRasterImageP ri = img) {
TRasterP ras = ri->getRaster();
@ -176,47 +209,52 @@ void ShiftTraceTool::updateBox() {
}
}
}
}
}
void ShiftTraceTool::updateData() {
TXsheet *xsh = TApp::instance()->getCurrentXsheet()->getXsheet();
int row = TApp::instance()->getCurrentFrame()->getFrame();
int col = TApp::instance()->getCurrentColumn()->getColumnIndex();
TXshCell cell = xsh->getCell(row, col);
m_box = TRectD();
for (int i = 0; i < 2; i++) m_row[i] = -1;
m_dpiAff = TAffine();
TApp *app = TApp::instance();
OnionSkinMask osm =
TApp::instance()->getCurrentOnionSkin()->getOnionSkinMask();
int previousOffset = osm.getShiftTraceGhostFrameOffset(0);
int forwardOffset = osm.getShiftTraceGhostFrameOffset(1);
// we must find the prev (m_row[0]) and next (m_row[1]) reference images
// (either might not exist)
// see also stage.cpp, StageBuilder::addCellWithOnionSkin
if (cell.isEmpty()) {
// current cell is empty. search for the prev ref img
int r = row - 1;
while (r >= 0 && xsh->getCell(r, col).getSimpleLevel() == 0) r--;
if (r >= 0) m_row[0] = r;
// else prev drawing doesn't exist : nothing to do
} else {
// current cell is not empty
// search for prev ref img
TXshSimpleLevel *sl = cell.getSimpleLevel();
int r = row - 1;
if (r >= 0) {
TXshCell otherCell = xsh->getCell(r, col);
if (otherCell.getSimpleLevel() == sl) {
// find the span start
while (r - 1 >= 0 && xsh->getCell(r - 1, col) == otherCell) r--;
if (app->getCurrentFrame()->isEditingScene()) {
TXsheet *xsh = app->getCurrentXsheet()->getXsheet();
int row = app->getCurrentFrame()->getFrame();
int col = app->getCurrentColumn()->getColumnIndex();
TXshCell cell = xsh->getCell(row, col);
int r;
r = row + previousOffset;
if (r >= 0 && xsh->getCell(r, col) != cell &&
(cell.getSimpleLevel() == 0 ||
xsh->getCell(r, col).getSimpleLevel() == cell.getSimpleLevel())) {
m_row[0] = r;
}
}
// search for next ref img
r = row + 1;
while (xsh->getCell(r, col) == cell) r++;
// first cell after the current span has the same level
if (xsh->getCell(r, col).getSimpleLevel() == sl) m_row[1] = r;
r = row + forwardOffset;
if (r >= 0 && xsh->getCell(r, col) != cell &&
(cell.getSimpleLevel() == 0 ||
xsh->getCell(r, col).getSimpleLevel() == cell.getSimpleLevel())) {
m_row[1] = r;
}
}
// on editing level
else {
TXshLevel *level = app->getCurrentLevel()->getLevel();
if (level) {
TXshSimpleLevel *sl = level->getSimpleLevel();
if (sl) {
TFrameId fid = app->getCurrentFrame()->getFid();
int row = sl->guessIndex(fid);
m_row[0] = row + previousOffset;
m_row[1] = row + forwardOffset;
}
}
}
updateBox();
}
@ -269,34 +307,41 @@ void ShiftTraceTool::drawDot(const TPointD &center, double r,
tglDrawCircle(center, r);
}
void ShiftTraceTool::drawControlRect() {
void ShiftTraceTool::drawControlRect() { // TODO
if (m_ghostIndex < 0 || m_ghostIndex > 1) return;
int row = m_row[m_ghostIndex];
if (row < 0) return;
int col = TApp::instance()->getCurrentColumn()->getColumnIndex();
TXsheet *xsh = TApp::instance()->getCurrentXsheet()->getXsheet();
TXshCell cell = xsh->getCell(row, col);
if (cell.isEmpty()) return;
TImageP img = cell.getImage(false);
if (!img) return;
TRectD box;
if (TRasterImageP ri = img) {
TRasterP ras = ri->getRaster();
box =
(convert(ras->getBounds()) - ras->getCenterD()) * ri->getSubsampling();
} else if (TToonzImageP ti = img) {
TRasterP ras = ti->getRaster();
box =
(convert(ras->getBounds()) - ras->getCenterD()) * ti->getSubsampling();
} else if (TVectorImageP vi = img) {
box = vi->getBBox();
} else {
return;
}
TRectD box = m_box;
if (box.isEmpty()) return;
glPushMatrix();
tglMultMatrix(getGhostAff());
TPixel32 color;
color = m_highlightedGadget == TranslateGadget ? TPixel32(200, 100, 100)
// draw onion-colored rectangle to indicate which ghost is grabbed
{
TPixel32 frontOniColor, backOniColor;
bool inksOnly;
Preferences::instance()->getOnionData(frontOniColor, backOniColor,
inksOnly);
color = (m_ghostIndex == 0) ? backOniColor : frontOniColor;
double unit = sqrt(tglGetPixelSize2());
unit *= getDevPixRatio();
TRectD coloredBox = box.enlarge(3.0 * unit);
tglColor(color);
glBegin(GL_LINE_STRIP);
glVertex2d(coloredBox.x0, coloredBox.y0);
glVertex2d(coloredBox.x1, coloredBox.y0);
glVertex2d(coloredBox.x1, coloredBox.y1);
glVertex2d(coloredBox.x0, coloredBox.y1);
glVertex2d(coloredBox.x0, coloredBox.y0);
glEnd();
}
color = m_highlightedGadget == TranslateGadget
? TPixel32(200, 100, 100)
: m_highlightedGadget == RotateGadget ? TPixel32(100, 200, 100)
: TPixel32(120, 120, 120);
tglColor(color);
glBegin(GL_LINE_STRIP);
@ -306,16 +351,16 @@ void ShiftTraceTool::drawControlRect() {
glVertex2d(box.x0, box.y1);
glVertex2d(box.x0, box.y0);
glEnd();
color =
m_highlightedGadget == 2000 ? TPixel32(200, 100, 100) : TPixel32::White;
color = m_highlightedGadget == ScaleGadget ? TPixel32(200, 100, 100)
: TPixel32::White;
double r = 4 * sqrt(tglGetPixelSize2());
drawDot(box.getP00(), r, color);
drawDot(box.getP01(), r, color);
drawDot(box.getP10(), r, color);
drawDot(box.getP11(), r, color);
if (m_curveStatus == NoCurve) {
color =
m_highlightedGadget == 2001 ? TPixel32(200, 100, 100) : TPixel32::White;
color = m_highlightedGadget == MoveCenterGadget ? TPixel32(200, 100, 100)
: TPixel32::White;
TPointD c = m_center[m_ghostIndex];
drawDot(c, r, color);
}
@ -327,18 +372,20 @@ void ShiftTraceTool::drawCurve() {
double r = 4 * sqrt(tglGetPixelSize2());
double u = getPixelSize();
if (m_curveStatus == TwoPointsCurve) {
TPixel32 color =
m_highlightedGadget == 1000 ? TPixel32(200, 100, 100) : TPixel32::White;
TPixel32 color = m_highlightedGadget == CurveP0Gadget
? TPixel32(200, 100, 100)
: TPixel32::White;
drawDot(m_p0, r, color);
glColor3d(0.2, 0.2, 0.2);
tglDrawSegment(m_p0, m_p1);
drawDot(m_p1, r, TPixel32::Red);
} else if (m_curveStatus == ThreePointsCurve) {
TPixel32 color =
m_highlightedGadget == 1000 ? TPixel32(200, 100, 100) : TPixel32::White;
TPixel32 color = m_highlightedGadget == CurveP0Gadget
? TPixel32(200, 100, 100)
: TPixel32::White;
drawDot(m_p0, r, color);
color =
m_highlightedGadget == 1001 ? TPixel32(200, 100, 100) : TPixel32::White;
color = m_highlightedGadget == CurveP1Gadget ? TPixel32(200, 100, 100)
: TPixel32::White;
drawDot(m_p1, r, color);
glColor3d(0.2, 0.2, 0.2);
@ -364,8 +411,8 @@ void ShiftTraceTool::drawCurve() {
} else {
tglDrawSegment(m_p0, m_p1);
}
color =
m_highlightedGadget == 1002 ? TPixel32(200, 100, 100) : TPixel32::White;
color = m_highlightedGadget == CurvePmGadget ? TPixel32(200, 100, 100)
: TPixel32::White;
drawDot(m_p2, r, color);
}
}
@ -376,16 +423,18 @@ ShiftTraceTool::GadgetId ShiftTraceTool::getGadget(const TPointD &p) {
gadgets.push_back(std::make_pair(m_p1, CurveP1Gadget));
gadgets.push_back(std::make_pair(m_p2, CurvePmGadget));
TAffine aff = getGhostAff();
double pixelSize = getPixelSize();
double d = 15 * pixelSize; // offset for rotation handle
if (0 <= m_ghostIndex && m_ghostIndex < 2) {
gadgets.push_back(std::make_pair(aff * m_box.getP00(), RotateGadget));
gadgets.push_back(std::make_pair(aff * m_box.getP01(), RotateGadget));
gadgets.push_back(std::make_pair(aff * m_box.getP10(), RotateGadget));
gadgets.push_back(std::make_pair(aff * m_box.getP11(), RotateGadget));
gadgets.push_back(std::make_pair(aff * m_box.getP00(), ScaleGadget));
gadgets.push_back(std::make_pair(aff * m_box.getP01(), ScaleGadget));
gadgets.push_back(std::make_pair(aff * m_box.getP10(), ScaleGadget));
gadgets.push_back(std::make_pair(aff * m_box.getP11(), ScaleGadget));
gadgets.push_back(
std::make_pair(aff * m_center[m_ghostIndex], MoveCenterGadget));
}
int k = -1;
double minDist2 = pow(10 * getPixelSize(), 2);
double minDist2 = pow(10 * pixelSize, 2);
for (int i = 0; i < (int)gadgets.size(); i++) {
double d2 = norm2(gadgets[i].first - p);
if (d2 < minDist2) {
@ -398,7 +447,6 @@ ShiftTraceTool::GadgetId ShiftTraceTool::getGadget(const TPointD &p) {
// rect-point
if (0 <= m_ghostIndex && m_ghostIndex < 2) {
TPointD q = aff.inv() * p;
double big = 1.0e6;
double d = big, x = 0, y = 0;
if (m_box.x0 < q.x && q.x < m_box.x1) {
@ -414,8 +462,8 @@ ShiftTraceTool::GadgetId ShiftTraceTool::getGadget(const TPointD &p) {
}
}
if (m_box.y0 < q.y && q.y < m_box.y1) {
double d0 = fabs(m_box.x0 - q.y);
double d1 = fabs(m_box.x1 - q.y);
double d0 = fabs(m_box.x0 - q.x);
double d1 = fabs(m_box.x1 - q.x);
if (d0 < d) {
d = d0;
y = q.y;
@ -431,9 +479,16 @@ ShiftTraceTool::GadgetId ShiftTraceTool::getGadget(const TPointD &p) {
TPointD pp = aff * TPointD(x, y);
double d = norm(p - pp);
if (d < 10 * getPixelSize()) {
if (m_box.contains(q))
return TranslateGadget;
else
return RotateGadget;
}
}
if (m_box.contains(q))
return NoGadget_InBox;
else
return NoGadget;
}
return NoGadget;
}
@ -450,31 +505,46 @@ void ShiftTraceTool::leftButtonDown(const TPointD &pos, const TMouseEvent &e) {
m_gadget = m_highlightedGadget;
m_oldPos = m_startPos = pos;
if (m_gadget == NoGadget) {
if (m_gadget == NoGadget || m_gadget == NoGadget_InBox) {
if (!e.isCtrlPressed()) {
if (m_gadget == NoGadget_InBox)
m_gadget = TranslateGadget;
else
m_gadget = RotateGadget;
// m_curveStatus = NoCurve;
}
int row = getViewer()->posToRow(e.m_pos, 5 * getPixelSize(), false);
if (row >= 0) {
int currentRow = getFrame();
int index = -1;
TApp *app = TApp::instance();
if (app->getCurrentFrame()->isEditingScene()) {
int currentRow = getFrame();
if (m_row[0] >= 0 && row <= currentRow)
index = 0;
else if (m_row[1] >= 0 && row > currentRow)
index = 1;
} else {
if (m_row[0] == row)
index = 0;
else if (m_row[1] == row)
index = 1;
}
if (index >= 0) {
m_ghostIndex = index;
updateBox();
m_gadget = TranslateGadget;
m_highlightedGadget = TranslateGadget;
}
}
}
if (!e.isCtrlPressed()) {
m_gadget = TranslateGadget;
// m_curveStatus = NoCurve;
}
}
m_oldAff = m_aff[m_ghostIndex];
invalidate();
}
void ShiftTraceTool::leftButtonDrag(const TPointD &pos, const TMouseEvent &) {
if (m_gadget == NoGadget) {
void ShiftTraceTool::leftButtonDrag(const TPointD &pos, const TMouseEvent &e) {
if (m_gadget == NoGadget || m_gadget == NoGadget_InBox) {
if (norm(pos - m_oldPos) > 10 * getPixelSize()) {
m_curveStatus = TwoPointsCurve;
m_p0 = m_oldPos;
@ -512,6 +582,17 @@ void ShiftTraceTool::leftButtonDrag(const TPointD &pos, const TMouseEvent &) {
TPointD delta = pos - m_oldPos;
m_oldPos = pos;
m_aff[m_ghostIndex] = TTranslation(delta) * m_aff[m_ghostIndex];
} else if (m_gadget == ScaleGadget) {
TAffine aff = getGhostAff();
TPointD c = aff * m_center[m_ghostIndex];
TPointD a = m_oldPos - c;
TPointD b = pos - c;
if (e.isShiftPressed())
m_aff[m_ghostIndex] = m_oldAff * TScale(b.x / a.x, b.y / a.y);
else {
double scale = std::max(b.x / a.x, b.y / a.y);
m_aff[m_ghostIndex] = m_oldAff * TScale(scale, scale);
}
}
updateGhost();
@ -541,12 +622,21 @@ void ShiftTraceTool::draw() {
}
int ShiftTraceTool::getCursorId() const {
if (m_highlightedGadget == RotateGadget)
if (m_highlightedGadget == RotateGadget || m_highlightedGadget == NoGadget)
return ToolCursor::RotateCursor;
else if (m_highlightedGadget == ScaleGadget)
return ToolCursor::ScaleCursor;
else if (isCurveGadget(m_highlightedGadget))
return ToolCursor::PinchCursor;
else
else // Curve Points, TranslateGadget, NoGadget_InBox
return ToolCursor::MoveCursor;
}
bool ShiftTraceTool::isEventAcceptable(QEvent *e) {
// F1, F2 and F3 keys are used for flipping
QKeyEvent *keyEvent = static_cast<QKeyEvent *>(e);
int key = keyEvent->key();
return (Qt::Key_F1 <= key && key <= Qt::Key_F3);
}
ShiftTraceTool shiftTraceTool;

View file

@ -561,6 +561,105 @@ void RowArea::drawCurrentTimeLine(QPainter &p) {
//-----------------------------------------------------------------------------
void RowArea::drawShiftTraceMarker(QPainter &p) {
TApp *app = TApp::instance();
OnionSkinMask osMask = app->getCurrentOnionSkin()->getOnionSkinMask();
TXsheet *xsh = app->getCurrentScene()->getScene()->getXsheet();
assert(xsh);
int currentRow = m_viewer->getCurrentRow();
int frameAdj = m_viewer->getFrameZoomAdjustment();
// get onion colors
TPixel frontPixel, backPixel;
bool inksOnly;
Preferences::instance()->getOnionData(frontPixel, backPixel, inksOnly);
QColor frontColor((int)frontPixel.r, (int)frontPixel.g, (int)frontPixel.b);
QColor backColor((int)backPixel.r, (int)backPixel.g, (int)backPixel.b);
// draw lines to ghost frames
int prevOffset = osMask.getShiftTraceGhostFrameOffset(0);
int forwardOffset = osMask.getShiftTraceGhostFrameOffset(1);
QRect onionRect =
m_viewer->orientation()->rect(PredefinedRect::SHIFTTRACE_DOT);
int onionCenter_frame =
m_viewer->orientation()->frameSide(onionRect).middle();
int onionCenter_layer =
m_viewer->orientation()->layerSide(onionRect).middle();
if (currentRow > 0 && prevOffset < 0) // previous ghost
{
int layerAxis = onionCenter_layer;
int fromFrameAxis = m_viewer->rowToFrameAxis(currentRow + prevOffset) +
onionCenter_frame - (frameAdj / 2);
int toFrameAxis = m_viewer->rowToFrameAxis(currentRow) + onionCenter_frame -
(frameAdj / 2);
QLine verticalLine = m_viewer->orientation()->verticalLine(
layerAxis, NumberRange(fromFrameAxis, toFrameAxis));
p.setPen(backColor);
p.setBrush(Qt::NoBrush);
p.drawLine(verticalLine);
}
if (forwardOffset > 0) // forward ghost
{
int layerAxis = onionCenter_layer;
int fromFrameAxis = m_viewer->rowToFrameAxis(currentRow) +
onionCenter_frame - (frameAdj / 2);
int toFrameAxis = m_viewer->rowToFrameAxis(currentRow + forwardOffset) +
onionCenter_frame - (frameAdj / 2);
QLine verticalLine = m_viewer->orientation()->verticalLine(
layerAxis, NumberRange(fromFrameAxis, toFrameAxis));
p.setPen(frontColor);
p.setBrush(Qt::NoBrush);
p.drawLine(verticalLine);
}
if (!m_viewer->orientation()->isVerticalTimeline())
drawCurrentTimeIndicator(p);
// draw dots
std::vector<int> offsVec = {prevOffset, 0, forwardOffset};
std::vector<QColor> colorsVec = {backColor, QColor(0, 162, 232), frontColor};
QFont currentFont = p.font();
QFont tmpFont = p.font();
tmpFont.setPointSize(7);
p.setFont(tmpFont);
for (int i = 0; i < 3; i++) {
if (i != 1 && offsVec[i] == 0) continue;
p.setPen(colorsVec[i]);
p.setBrush(Qt::gray);
QPoint topLeft =
m_viewer->positionToXY(CellPosition(currentRow + offsVec[i], 0));
if (!m_viewer->orientation()->isVerticalTimeline()) topLeft.setY(0);
QRect dotRect = m_viewer->orientation()
->rect(PredefinedRect::SHIFTTRACE_DOT)
.translated(topLeft);
dotRect.adjust(-frameAdj / 2, 0, -frameAdj / 2, 0);
p.drawRect(dotRect);
// draw shortcut numbers
p.setPen(Qt::black);
p.drawText(dotRect, Qt::AlignCenter, QString::number(i + 1));
}
p.setFont(currentFont);
//-- onion placement hint under mouse
if (m_showOnionToSet == ShiftTraceGhost) {
p.setPen(QColor(255, 255, 0));
p.setBrush(QColor(255, 255, 0, 180));
QPoint topLeft = m_viewer->positionToXY(CellPosition(m_row, 0));
if (!m_viewer->orientation()->isVerticalTimeline()) topLeft.setY(0);
QRect dotRect = m_viewer->orientation()
->rect(PredefinedRect::SHIFTTRACE_DOT)
.translated(topLeft);
dotRect.adjust(-frameAdj / 2, 0, -frameAdj / 2, 0);
p.drawRect(dotRect);
}
}
//-----------------------------------------------------------------------------
namespace {
TStageObjectId getAncestor(TXsheet *xsh, TStageObjectId id) {
@ -672,7 +771,9 @@ void RowArea::paintEvent(QPaintEvent *event) {
drawRows(p, r0, r1);
if (TApp::instance()->getCurrentFrame()->isEditingScene()) {
if (Preferences::instance()->isOnionSkinEnabled())
if (CommandManager::instance()->getAction(MI_ShiftTrace)->isChecked())
drawShiftTraceMarker(p);
else if (Preferences::instance()->isOnionSkinEnabled())
drawOnionSkinSelection(p);
else if (Preferences::instance()->isCurrentTimelineIndicatorEnabled() &&
!m_viewer->orientation()->isVerticalTimeline())
@ -711,7 +812,38 @@ void RowArea::mousePressEvent(QMouseEvent *event) {
QPoint mouseInCell = event->pos() - topLeft;
int frameAdj = m_viewer->getFrameZoomAdjustment();
if (Preferences::instance()->isOnionSkinEnabled() &&
if (CommandManager::instance()->getAction(MI_ShiftTrace)->isChecked() &&
o->rect(PredefinedRect::SHIFTTRACE_DOT_AREA)
.adjusted(0, 0, -frameAdj, 0)
.contains(mouseInCell)) {
// Reset ghosts to neighbor frames
if (row == currentFrame)
OnioniSkinMaskGUI::resetShiftTraceFrameOffset();
else {
OnionSkinMask osMask =
TApp::instance()->getCurrentOnionSkin()->getOnionSkinMask();
int prevOffset = osMask.getShiftTraceGhostFrameOffset(0);
int forwardOffset = osMask.getShiftTraceGhostFrameOffset(1);
// Hide previous ghost
if (row == currentFrame + prevOffset)
osMask.setShiftTraceGhostFrameOffset(0, 0);
// Hide forward ghost
else if (row == currentFrame + forwardOffset)
osMask.setShiftTraceGhostFrameOffset(1, 0);
// Move previous ghost
else if (row < currentFrame)
osMask.setShiftTraceGhostFrameOffset(0, row - currentFrame);
// Move forward ghost
else
osMask.setShiftTraceGhostFrameOffset(1, row - currentFrame);
TApp::instance()->getCurrentOnionSkin()->setOnionSkinMask(osMask);
}
TApp::instance()->getCurrentOnionSkin()->notifyOnionSkinMaskChanged();
return;
} else if (!CommandManager::instance()
->getAction(MI_ShiftTrace)
->isChecked() &&
Preferences::instance()->isOnionSkinEnabled() &&
o->rect(PredefinedRect::ONION_AREA)
.adjusted(0, 0, -frameAdj, 0)
.contains(mouseInCell)) {
@ -833,8 +965,38 @@ void RowArea::mouseMoveEvent(QMouseEvent *event) {
if (!m_viewer->orientation()->isVerticalTimeline()) topLeft.setY(0);
QPoint mouseInCell = event->pos() - topLeft;
if (row < 0) return;
m_tooltip = tr("");
// whether to show ability to move the shift and trace ghost frame
if (CommandManager::instance()->getAction(MI_ShiftTrace)->isChecked()) {
if (o->rect(PredefinedRect::SHIFTTRACE_DOT_AREA)
.adjusted(0, 0, -frameAdj, 0)
.contains(mouseInCell)) {
m_showOnionToSet = ShiftTraceGhost;
OnionSkinMask osMask =
TApp::instance()->getCurrentOnionSkin()->getOnionSkinMask();
int prevOffset = osMask.getShiftTraceGhostFrameOffset(0);
int forwardOffset = osMask.getShiftTraceGhostFrameOffset(1);
if (row == currentRow)
m_tooltip =
tr("Click to Reset Shift & Trace Markers to Neighbor Frames\nHold "
"F2 Key on the Viewer to Show This Frame Only");
else if (row == currentRow + prevOffset)
m_tooltip =
tr("Click to Hide This Frame from Shift & Trace\nHold F1 Key on "
"the Viewer to Show This Frame Only");
else if (row == currentRow + forwardOffset)
m_tooltip =
tr("Click to Hide This Frame from Shift & Trace\nHold F3 Key on "
"the Viewer to Show This Frame Only");
else
m_tooltip = tr("Click to Move Shift & Trace Marker");
}
}
// whether to show ability to set onion marks
if (Preferences::instance()->isOnionSkinEnabled() && row != currentRow) {
else if (Preferences::instance()->isOnionSkinEnabled() && row != currentRow) {
if (o->rect(PredefinedRect::ONION_FIXED_DOT_AREA)
.adjusted(0, 0, -frameAdj, 0)
.contains(mouseInCell))
@ -884,6 +1046,7 @@ void RowArea::mouseMoveEvent(QMouseEvent *event) {
QPainterPath endArrow =
o->path(PredefinedPath::END_PLAY_RANGE).translated(base1);
if (!m_tooltip.isEmpty()) return;
if (startArrow.contains(m_pos))
m_tooltip = tr("Playback Start Marker");
else if (endArrow.contains(m_pos))
@ -904,8 +1067,6 @@ void RowArea::mouseMoveEvent(QMouseEvent *event) {
m_tooltip = tr("Fixed Onion Skin Toggle");
else if (m_showOnionToSet == Mos)
m_tooltip = tr("Relative Onion Skin Toggle");
else
m_tooltip = tr("");
}
//-----------------------------------------------------------------------------

View file

@ -26,8 +26,9 @@ class RowArea final : public QWidget {
enum ShowOnionToSetFlag {
None = 0,
Fos,
Mos
} m_showOnionToSet; // TODO:明日はこれをFos,Mosどちらをハイライトしているのか判定させる
Mos,
ShiftTraceGhost
} m_showOnionToSet;
enum Direction { up = 0, down };
@ -51,6 +52,7 @@ class RowArea final : public QWidget {
void drawPinnedCenterKeys(QPainter &p, int r0, int r1);
void drawCurrentTimeIndicator(QPainter &p);
void drawCurrentTimeLine(QPainter &p);
void drawShiftTraceMarker(QPainter &p);
DragTool *getDragTool() const;
void setDragTool(DragTool *dragTool);

View file

@ -64,6 +64,8 @@ void OnionSkinMask::clear() {
m_ghostAff[1] = TAffine();
m_ghostCenter[0] = TPointD();
m_ghostCenter[1] = TPointD();
m_ghostFrame[0] = 0;
m_ghostFrame[1] = 0;
}
//-------------------------------------------------------------------

View file

@ -19,6 +19,7 @@ const int ONION_DOT_SIZE = 8;
const int PINNED_SIZE = 10;
const int FRAME_MARKER_SIZE = 4;
const int FOLDED_CELL_SIZE = 9;
const int SHIFTTRACE_DOT_SIZE = 12;
}
class TopToBottomOrientation : public Orientation {
@ -37,6 +38,7 @@ class TopToBottomOrientation : public Orientation {
const int ICON_WIDTH = 18;
const int ICON_HEIGHT = 18;
const int TRACKLEN = 60;
const int SHIFTTRACE_DOT_OFFSET = 3;
public:
TopToBottomOrientation();
@ -97,6 +99,7 @@ class LeftToRightOrientation : public Orientation {
const int FOLDED_LAYER_HEADER_HEIGHT = 8;
const int FOLDED_LAYER_HEADER_WIDTH = LAYER_HEADER_WIDTH;
const int TRACKLEN = 60;
const int SHIFTTRACE_DOT_OFFSET = 5;
public:
LeftToRightOrientation();
@ -347,6 +350,12 @@ TopToBottomOrientation::TopToBottomOrientation() {
PredefinedRect::PREVIEW_FRAME_AREA,
QRect(PLAY_RANGE_X, 0, (FRAME_HEADER_WIDTH - PLAY_RANGE_X), CELL_HEIGHT));
addRect(PredefinedRect::SHIFTTRACE_DOT,
QRect(SHIFTTRACE_DOT_OFFSET, (CELL_HEIGHT - SHIFTTRACE_DOT_SIZE) / 2,
SHIFTTRACE_DOT_SIZE, SHIFTTRACE_DOT_SIZE));
addRect(PredefinedRect::SHIFTTRACE_DOT_AREA,
QRect(SHIFTTRACE_DOT_OFFSET, 0, SHIFTTRACE_DOT_SIZE, CELL_HEIGHT));
// Column viewer
addRect(PredefinedRect::LAYER_HEADER,
QRect(0, 1, CELL_WIDTH, use_header_height - 3));
@ -953,6 +962,13 @@ LeftToRightOrientation::LeftToRightOrientation() {
PredefinedRect::PREVIEW_FRAME_AREA,
QRect(0, PLAY_RANGE_Y, CELL_WIDTH, (FRAME_HEADER_HEIGHT - PLAY_RANGE_Y)));
addRect(PredefinedRect::SHIFTTRACE_DOT,
QRect((CELL_WIDTH - SHIFTTRACE_DOT_SIZE) / 2, SHIFTTRACE_DOT_OFFSET,
SHIFTTRACE_DOT_SIZE, SHIFTTRACE_DOT_SIZE)
.adjusted(-1, 0, -1, 0));
addRect(PredefinedRect::SHIFTTRACE_DOT_AREA,
QRect(0, SHIFTTRACE_DOT_OFFSET, CELL_WIDTH, SHIFTTRACE_DOT_SIZE));
// Column viewer
addRect(PredefinedRect::LAYER_HEADER,
QRect(1, 0, LAYER_HEADER_WIDTH - 2, CELL_HEIGHT));

View file

@ -398,7 +398,29 @@ void StageBuilder::addCell(PlayerSet &players, ToonzScene *scene, TXsheet *xsh,
}
if (m_shiftTraceGhostId != NO_GHOST) {
if (m_shiftTraceGhostId != TRACED) player.m_opacity = 127;
// if F1, F2 or F3 key is pressed, then draw only the corresponding ghost
int flipKey = m_onionSkinMask.getGhostFlipKey();
if (Qt::Key_F1 <= flipKey && flipKey <= Qt::Key_F3) {
if (m_shiftTraceGhostId == TRACED && flipKey == Qt::Key_F2) {
players.push_back(player);
} else if (m_shiftTraceGhostId == FIRST_GHOST &&
flipKey == Qt::Key_F1) {
player.m_placement =
m_onionSkinMask.getShiftTraceGhostAff(0) * player.m_placement;
players.push_back(player);
} else if (m_shiftTraceGhostId == SECOND_GHOST &&
flipKey == Qt::Key_F3) {
player.m_placement =
m_onionSkinMask.getShiftTraceGhostAff(1) * player.m_placement;
players.push_back(player);
}
return;
}
else {
if (m_shiftTraceGhostId != TRACED)
player.m_opacity =
UCHAR(255.0 * (1.0 - OnionSkinMask::getOnionSkinFade(1)));
int opacity = player.m_opacity;
player.m_bingoOrder = 10;
if (m_onionSkinMask.getShiftTraceStatus() !=
@ -418,6 +440,7 @@ void StageBuilder::addCell(PlayerSet &players, ToonzScene *scene, TXsheet *xsh,
}
}
}
}
players.push_back(player);
} else if (TXshChildLevel *cl = xl->getChildLevel()) {
@ -511,31 +534,26 @@ void StageBuilder::addCellWithOnionSkin(PlayerSet &players, ToonzScene *scene,
if (m_onionSkinMask.isShiftTraceEnabled() && col == m_currentColumnIndex) {
TXshCell cell = xsh->getCell(row, col);
int r = row - 1;
// r,col can be a hold. find its starting point
for (; r - 1 >= 0 && xsh->getCell(r - 1, col) == cell; r--)
;
if (cell.isEmpty()) r--;
if (r >= 0 &&
// First Ghost
int r;
r = row + m_onionSkinMask.getShiftTraceGhostFrameOffset(0);
if (r >= 0 && xsh->getCell(r, col) != cell &&
(cell.getSimpleLevel() == 0 ||
xsh->getCell(r, col).getSimpleLevel() == cell.getSimpleLevel())) {
m_shiftTraceGhostId = FIRST_GHOST;
addCell(players, scene, xsh, r, col, level);
}
TXshCell otherCell;
if (cell.getSimpleLevel() != 0) {
for (r = row + 1; (otherCell = xsh->getCell(r, col)) == cell; r++)
;
if (cell.getSimpleLevel() == 0 ||
otherCell.getSimpleLevel() == cell.getSimpleLevel()) {
r = row + m_onionSkinMask.getShiftTraceGhostFrameOffset(1);
if (r >= 0 && xsh->getCell(r, col) != cell &&
(cell.getSimpleLevel() == 0 ||
xsh->getCell(r, col).getSimpleLevel() == cell.getSimpleLevel())) {
m_shiftTraceGhostId = SECOND_GHOST;
addCell(players, scene, xsh, r, col, level);
}
}
// draw current working frame
if (!cell.isEmpty()) {
m_shiftTraceGhostId = TRACED;
addCell(players, scene, xsh, row, col, level);
@ -632,10 +650,77 @@ void StageBuilder::addFrame(PlayerSet &players, ToonzScene *scene, TXsheet *xsh,
void StageBuilder::addSimpleLevelFrame(PlayerSet &players,
TXshSimpleLevel *level,
const TFrameId &fid) {
auto addGhost = [&](int ghostIndex, int ghostRow, bool fullOpac = false) {
const TFrameId &ghostFid = level->index2fid(ghostRow);
Player player;
player.m_sl = level;
player.m_frame = level->guessIndex(ghostFid);
player.m_fid = ghostFid;
player.m_isCurrentColumn = true;
player.m_isCurrentXsheetLevel = true;
player.m_isEditingLevel = true;
player.m_currentFrameId = m_currentFrameId;
player.m_isGuidedDrawingEnabled = m_isGuidedDrawingEnabled;
player.m_isVisibleinOSM = ghostRow >= 0;
player.m_onionSkinDistance = m_onionSkinDistance;
player.m_dpiAff = getDpiAffine(level, ghostFid);
player.m_ancestorColumnIndex = -1;
if (fullOpac) {
player.m_placement = m_onionSkinMask.getShiftTraceGhostAff(ghostIndex) *
player.m_placement;
players.push_back(player);
return;
}
if (m_shiftTraceGhostId != TRACED)
player.m_opacity =
UCHAR(255.0 * (1.0 - OnionSkinMask::getOnionSkinFade(1)));
;
int opacity = player.m_opacity;
player.m_bingoOrder = 10;
if (m_onionSkinMask.getShiftTraceStatus() !=
OnionSkinMask::ENABLED_WITHOUT_GHOST_MOVEMENTS) {
player.m_opacity = 30;
players.push_back(player);
player.m_opacity = opacity;
player.m_placement = m_onionSkinMask.getShiftTraceGhostAff(ghostIndex) *
player.m_placement;
}
players.push_back(player);
};
int index = -1;
int row = level->guessIndex(fid);
if (!m_onionSkinMask.isEmpty() && m_onionSkinMask.isEnabled()) {
// Shift & Trace
if (m_onionSkinMask.isShiftTraceEnabled()) {
int previousOffset = m_onionSkinMask.getShiftTraceGhostFrameOffset(0);
int forwardOffset = m_onionSkinMask.getShiftTraceGhostFrameOffset(1);
// If F1, F2 or F3 key is pressed, then only
// display the corresponding ghost
int flipKey = m_onionSkinMask.getGhostFlipKey();
if (Qt::Key_F1 <= flipKey && flipKey <= Qt::Key_F3) {
if (flipKey == Qt::Key_F1 && previousOffset != 0) {
addGhost(0, row + previousOffset, true);
return;
} else if (flipKey == Qt::Key_F3 && forwardOffset != 0) {
addGhost(1, row + forwardOffset, true);
return;
}
}
else {
// draw the first ghost
if (previousOffset != 0) addGhost(0, row + previousOffset);
if (forwardOffset != 0) addGhost(1, row + forwardOffset);
}
}
// Onion Skin
else if (!m_onionSkinMask.isEmpty() && m_onionSkinMask.isEnabled()) {
std::vector<int> rows;
m_onionSkinMask.getAll(row, rows);