123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669 |
- /*
- * Copyright (c) Contributors to the Open 3D Engine Project.
- * For complete copyright and license terms please see the LICENSE at the root of this distribution.
- *
- * SPDX-License-Identifier: Apache-2.0 OR MIT
- *
- */
- #include "EditorDefs.h"
- #include "TimelineCtrl.h"
- // Qt
- #include <QPainter>
- // Editor
- #include "ScopedVariableSetter.h"
- #include "GridUtils.h"
- QColor InterpolateColor(const QColor& c1, const QColor& c2, float fraction)
- {
- const int r = static_cast<int>(static_cast<float>(c2.red() - c1.red()) * fraction + c1.red());
- const int g = static_cast<int>(static_cast<float>(c2.green() - c1.green()) * fraction + c1.green());
- const int b = static_cast<int>(static_cast<float>(c2.blue() - c1.blue()) * fraction + c1.blue());
- return QColor(r, g, b);
- }
- //////////////////////////////////////////////////////////////////////////
- TimelineWidget::TimelineWidget(QWidget* parent /* = nullptr */)
- : QWidget(parent)
- {
- setMouseTracking(true);
- m_timeRange.start = 0;
- m_timeRange.end = 1;
- m_timeScale = 1;
- m_fTicksTextScale = 1.0f;
- m_fTimeMarker = -10;
- m_nTicksStep = 100;
- m_trackingMode = TRACKING_MODE_NONE;
- m_leftOffset = 0;
- m_scrollOffset = 0;
- m_ticksStep = 10.0f;
- m_grid.zoom.SetX(100);
- m_bIgnoreSetTime = false;
- m_pKeyTimeSet = nullptr;
- m_markerStyle = MARKER_STYLE_SECONDS;
- m_fps = 30.0f;
- m_copyKeyTimes = false;
- m_bTrackingSnapToFrames = false;
- }
- TimelineWidget::~TimelineWidget()
- {
- }
- /////////////////////////////////////////////////////////////////////////////
- // CTimelineCtrl message handlers
- //////////////////////////////////////////////////////////////////////////
- void TimelineWidget::resizeEvent(QResizeEvent* event)
- {
- Q_UNUSED(event);
- m_rcTimeline = rect();
- m_grid.rect = m_rcTimeline;
- }
- //////////////////////////////////////////////////////////////////////////
- int TimelineWidget::TimeToClient(float time)
- {
- return m_grid.WorldToClient(Vec2(time, 0)).x();
- }
- //////////////////////////////////////////////////////////////////////////
- float TimelineWidget::ClientToTime(int x)
- {
- return m_grid.ClientToWorld(QPoint(x, 0)).x;
- }
- //////////////////////////////////////////////////////////////////////////
- void TimelineWidget::paintEvent(QPaintEvent* event)
- {
- QPainter painter(this);
- QRect rcClient = rect();
- {
- //////////////////////////////////////////////////////////////////////////
- // Fill keys background.
- //////////////////////////////////////////////////////////////////////////
- const QRect rc = rcClient.intersected(event->rect());
- painter.fillRect(rc, palette().color(QPalette::Button));
- painter.drawRect(rc);
- //////////////////////////////////////////////////////////////////////////
- m_grid.CalculateGridLines();
- DrawTicks(&painter);
- }
- }
- //////////////////////////////////////////////////////////////////////////
- float TimelineWidget::SnapTime(float time)
- {
- double t = floor((double)time * m_ticksStep + 0.5);
- t = t / m_ticksStep;
- return static_cast<float>(t);
- }
- //////////////////////////////////////////////////////////////////////////
- void TimelineWidget::DrawTicks(QPainter* painter)
- {
- const QRectF rc = rect();
- const QPen pOldPen = painter->pen();
- const QPen ltgray(QColor(110, 110, 110));
- const QPen redpen(QColor(255, 0, 255));
- // Draw time ticks every tick step seconds.
- painter->setPen(ltgray);
- switch (m_markerStyle)
- {
- case MARKER_STYLE_SECONDS:
- DrawSecondTicks(painter);
- break;
- case MARKER_STYLE_FRAMES:
- DrawFrameTicks(painter);
- break;
- }
- painter->setPen(redpen);
- int x = TimeToClient(m_fTimeMarker);
- painter->setBrush(Qt::NoBrush);
- painter->drawRect(QRect(QPoint(x - 3, static_cast<int>(rc.top())), QPoint(x + 2, static_cast<int>(rc.bottom()))));
- painter->setPen(redpen);
- painter->drawLine(x, static_cast<int>(rc.top()), x, static_cast<int>(rc.bottom()));
- painter->setBrush(Qt::NoBrush);
- // Draw vertical line showing current time.
- {
- int x2 = TimeToClient(m_fTimeMarker);
- if (x2 > m_rcTimeline.left() && x2 < m_rcTimeline.right())
- {
- painter->setPen(QColor(255, 0, 255));
- painter->drawLine(x2, 0, x2, m_rcTimeline.bottom());
- }
- }
- // Draw the key times.
- painter->setPen(redpen);
- painter->setBrush(Qt::NoBrush);
- const QPen keySelectedPen(QColor(100, 255, 255));
- const QBrush keySelectedBrush(QColor(100, 255, 255));
- for (int keyTimeIndex = 0; m_pKeyTimeSet && keyTimeIndex < m_pKeyTimeSet->GetKeyTimeCount(); ++keyTimeIndex)
- {
- int keyCountBound = __max(m_pKeyTimeSet->GetKeyCountBound(), 1);
- int keyCount = __min(m_pKeyTimeSet->GetKeyCount(keyTimeIndex), keyCountBound);
- float colorCodeFraction = float(keyCount) / keyCountBound;
- const QColor keyMarkerCol = InterpolateColor(Qt::green, Qt::red, colorCodeFraction);
- const QPen keyPen(keyMarkerCol);
- const QBrush keyBrush(keyMarkerCol);
- bool keyTimeSelected = m_pKeyTimeSet && m_pKeyTimeSet->GetKeyTimeSelected(keyTimeIndex);
- painter->setBrush(keyTimeSelected ? keySelectedBrush : keyBrush);
- painter->setPen(keyTimeSelected ? keySelectedPen : keyPen);
- float keyTime = (m_pKeyTimeSet ? m_pKeyTimeSet->GetKeyTime(keyTimeIndex) : 0.0f);
- int x2 = TimeToClient(keyTime);
- painter->drawRect(QRect(QPoint(x2 - 1, static_cast<int>(rc.top())), QPoint(x2 + 2, static_cast<int>(rc.bottom()))));
- }
- painter->setPen(pOldPen);
- }
- //////////////////////////////////////////////////////////////////////////
- Range TimelineWidget::GetVisibleRange() const
- {
- Range r;
- r.start = (m_scrollOffset - m_leftOffset) / m_timeScale;
- r.end = r.start + (m_rcTimeline.width()) / m_timeScale;
- // Intersect range with global time range.
- r = m_timeRange & r;
- return r;
- }
- /////////////////////////////////////////////////////////////////////////////
- //Mouse Message Handlers
- //////////////////////////////////////////////////////////////////////////
- void TimelineWidget::mousePressEvent(QMouseEvent* event)
- {
- switch (event->button())
- {
- case Qt::LeftButton:
- OnLButtonDown(event->pos(), event->modifiers());
- break;
- case Qt::RightButton:
- OnRButtonDown(event->pos(), event->modifiers());
- break;
- }
- }
- void TimelineWidget::mouseReleaseEvent(QMouseEvent* event)
- {
- switch (event->button())
- {
- case Qt::LeftButton:
- OnLButtonUp(event->pos(), event->modifiers());
- break;
- case Qt::RightButton:
- OnRButtonUp(event->pos(), event->modifiers());
- break;
- }
- }
- namespace
- {
- const float EDITOR_FPS = 30.0f;
- float SnapTimeToFrame(float time) {return int((time * EDITOR_FPS) + 0.5f) * (1.0f / EDITOR_FPS); }
- }
- void TimelineWidget::OnLButtonDown(const QPoint& point, Qt::KeyboardModifiers modifiers)
- {
- if (m_trackingMode != TRACKING_MODE_NONE)
- {
- return;
- }
- Q_EMIT clicked();
- int hitKeyTimeIndex = HitKeyTimes(point);
- bool autoDeselect = !(modifiers& Qt::ControlModifier) && (m_pKeyTimeSet && ((hitKeyTimeIndex >= 0) ? !m_pKeyTimeSet->GetKeyTimeSelected(hitKeyTimeIndex) : true));
- for (int keyTimeIndex = 0; m_pKeyTimeSet && keyTimeIndex < m_pKeyTimeSet->GetKeyTimeCount(); ++keyTimeIndex)
- {
- bool shouldBeSelected;
- if (keyTimeIndex == hitKeyTimeIndex)
- {
- shouldBeSelected = (modifiers& Qt::ControlModifier) || !((modifiers& Qt::ShiftModifier) && m_pKeyTimeSet->GetKeyTimeSelected(keyTimeIndex));
- }
- else
- {
- shouldBeSelected = (!autoDeselect || (modifiers & Qt::ShiftModifier)) && m_pKeyTimeSet->GetKeyTimeSelected(keyTimeIndex);
- }
- m_pKeyTimeSet->SetKeyTimeSelected(keyTimeIndex, shouldBeSelected);
- }
- TrackingMode trackingMode = (hitKeyTimeIndex >= 0 ? TRACKING_MODE_MOVE_KEYS : TRACKING_MODE_NONE);
- if (trackingMode == TRACKING_MODE_NONE)
- {
- trackingMode = ((modifiers& Qt::ControlModifier) ? TRACKING_MODE_SELECTION_RANGE : TRACKING_MODE_SET_TIME);
- }
- StartTracking(trackingMode);
- switch (m_trackingMode)
- {
- case TRACKING_MODE_SET_TIME:
- {
- if (m_bTrackingSnapToFrames)
- {
- SetTimeMarker(SnapTimeToFrame(ClientToTime(point.x())));
- }
- else
- {
- SetTimeMarker(ClientToTime(point.x()));
- }
- CScopedVariableSetter<bool> ignoreSetTime(m_bIgnoreSetTime, true);
- Q_EMIT startChange();
- Q_EMIT change();
- }
- break;
- case TRACKING_MODE_MOVE_KEYS:
- m_bChangedKeyTimeSet = false;
- m_copyKeyTimes = (modifiers & Qt::ControlModifier ? true : false);
- break;
- case TRACKING_MODE_NONE:
- break;
- }
- m_lastPoint = point;
- update();
- }
- //////////////////////////////////////////////////////////////////////////
- void TimelineWidget::OnRButtonDown(const QPoint& point, [[maybe_unused]] Qt::KeyboardModifiers modifiers)
- {
- Q_EMIT clicked();
- if (m_trackingMode != TRACKING_MODE_NONE)
- {
- return;
- }
- StartTracking(TRACKING_MODE_SET_TIME);
- if (m_bTrackingSnapToFrames)
- {
- SetTimeMarker(SnapTimeToFrame(ClientToTime(point.x())));
- }
- else
- {
- SetTimeMarker(ClientToTime(point.x()));
- }
- CScopedVariableSetter<bool> ignoreSetTime(m_bIgnoreSetTime, true);
- Q_EMIT startChange();
- Q_EMIT change();
- update();
- }
- //////////////////////////////////////////////////////////////////////////
- void TimelineWidget::OnRButtonUp([[maybe_unused]] const QPoint& point, [[maybe_unused]] Qt::KeyboardModifiers modifiers)
- {
- switch (m_trackingMode)
- {
- case TRACKING_MODE_SET_TIME:
- {
- Q_EMIT endChange();
- }
- break;
- }
- if (m_trackingMode != TRACKING_MODE_NONE)
- {
- StopTracking();
- }
- }
- //////////////////////////////////////////////////////////////////////////
- void TimelineWidget::keyPressEvent(QKeyEvent* event)
- {
- if (event->matches(QKeySequence::Delete))
- {
- Q_EMIT deleteRequested();
- }
- if (event->key() == Qt::Key_Space && m_playCallback)
- {
- m_playCallback();
- }
- }
- //////////////////////////////////////////////////////////////////////////
- void TimelineWidget::mouseMoveEvent(QMouseEvent* event)
- {
- switch (m_trackingMode)
- {
- case TRACKING_MODE_SET_TIME:
- {
- if (m_bTrackingSnapToFrames)
- {
- SetTimeMarker(SnapTimeToFrame(ClientToTime(event->x())));
- }
- else
- {
- SetTimeMarker(ClientToTime(event->x()));
- }
- CScopedVariableSetter<bool> ignoreSetTime(m_bIgnoreSetTime, true);
- Q_EMIT change();
- }
- break;
- case TRACKING_MODE_MOVE_KEYS:
- {
- if (m_pKeyTimeSet && !m_bChangedKeyTimeSet)
- {
- m_bChangedKeyTimeSet = true;
- m_pKeyTimeSet->BeginEdittingKeyTimes();
- }
- const bool altClicked = (Qt::AltModifier & QApplication::queryKeyboardModifiers());
- float scale, offset;
- float startTime = ClientToTime(m_lastPoint.x());
- float endTime = ClientToTime(event->x());
- if (altClicked)
- {
- // Alt was pressed, so we should scale the key times rather than translate.
- // Calculate the scaling parameters (ie t1 = t0 * M + C).
- scale = 1.0f;
- if (fabsf(startTime - m_fTimeMarker) > 0.1)
- {
- scale = (endTime - m_fTimeMarker) / (startTime - m_fTimeMarker);
- }
- offset = endTime - startTime * scale;
- }
- else
- {
- // Simply move the keys.
- offset = endTime - startTime;
- scale = 1.0f;
- }
- MoveSelectedKeyTimes(scale, offset);
- }
- break;
- case TRACKING_MODE_SELECTION_RANGE:
- {
- float start = min(ClientToTime(m_lastPoint.x()), ClientToTime(event->x()));
- float end = max(ClientToTime(m_lastPoint.x()), ClientToTime(event->x()));
- SelectKeysInRange(start, end, !(event->modifiers() & Qt::ShiftModifier));
- m_lastPoint = event->pos();
- update();
- }
- break;
- case TRACKING_MODE_NONE:
- break;
- }
- //m_lastPoint = point;
- }
- //////////////////////////////////////////////////////////////////////////
- QString TimelineWidget::TimeToString(float time)
- {
- return QString::number(time, 'f', 3);
- }
- //////////////////////////////////////////////////////////////////////////
- void TimelineWidget::OnLButtonUp([[maybe_unused]] const QPoint& point, [[maybe_unused]] Qt::KeyboardModifiers modifiers)
- {
- switch (m_trackingMode)
- {
- case TRACKING_MODE_MOVE_KEYS:
- {
- if (m_pKeyTimeSet && m_bChangedKeyTimeSet)
- {
- m_pKeyTimeSet->EndEdittingKeyTimes();
- }
- }
- break;
- case TRACKING_MODE_SET_TIME:
- {
- Q_EMIT endChange();
- }
- break;
- case TRACKING_MODE_NONE:
- break;
- }
- if (m_trackingMode != TRACKING_MODE_NONE)
- {
- StopTracking();
- }
- }
- ///////////////////////////////////////////////////////////////////////////////
- void TimelineWidget::StartTracking(TrackingMode trackingMode)
- {
- m_trackingMode = trackingMode;
- }
- //////////////////////////////////////////////////////////////////////////
- void TimelineWidget::StopTracking()
- {
- if (!m_trackingMode)
- {
- return;
- }
- m_trackingMode = TRACKING_MODE_NONE;
- }
- //////////////////////////////////////////////////////////////////////////
- void TimelineWidget::SetTimeMarker(float fTime)
- {
- if (fTime < m_timeRange.start)
- {
- fTime = m_timeRange.start;
- }
- else if (fTime > m_timeRange.end)
- {
- fTime = m_timeRange.end;
- }
- if (fTime == m_fTimeMarker || m_bIgnoreSetTime)
- {
- return;
- }
- int x0 = TimeToClient(m_fTimeMarker);
- int x1 = TimeToClient(fTime);
- QRect rc(QPoint(x0, m_rcClient.top()), QPoint(x1, m_rcClient.bottom()));
- rc = rc.normalized();
- rc.adjust(-5, 0, 5, 0);
- update(rc);
- m_fTimeMarker = fTime;
- }
- //////////////////////////////////////////////////////////////////////////
- void TimelineWidget::SetZoom(float fZoom)
- {
- m_grid.zoom.SetX(fZoom);
- }
- //////////////////////////////////////////////////////////////////////////
- void TimelineWidget::SetOrigin(float fOffset)
- {
- m_grid.origin.SetX(fOffset);
- }
- //////////////////////////////////////////////////////////////////////////
- void TimelineWidget::SetKeyTimeSet(IKeyTimeSet* pKeyTimeSet)
- {
- m_pKeyTimeSet = pKeyTimeSet;
- }
- //////////////////////////////////////////////////////////////////////////
- int TimelineWidget::HitKeyTimes(const QPoint& point)
- {
- const int threshold = 3;
- int hitKeyTimeIndex = -1;
- for (int keyTimeIndex = 0; m_pKeyTimeSet && keyTimeIndex < m_pKeyTimeSet->GetKeyTimeCount(); ++keyTimeIndex)
- {
- float keyTime = (m_pKeyTimeSet ? m_pKeyTimeSet->GetKeyTime(keyTimeIndex) : 0.0f);
- int x = TimeToClient(keyTime);
- if (abs(point.x() - x) <= threshold)
- {
- hitKeyTimeIndex = keyTimeIndex;
- }
- }
- return hitKeyTimeIndex;
- }
- //////////////////////////////////////////////////////////////////////////
- void TimelineWidget::MoveSelectedKeyTimes(float scale, float offset)
- {
- std::vector<int> indices;
- for (int keyTimeIndex = 0; m_pKeyTimeSet && keyTimeIndex < m_pKeyTimeSet->GetKeyTimeCount(); ++keyTimeIndex)
- {
- if (m_pKeyTimeSet && m_pKeyTimeSet->GetKeyTimeSelected(keyTimeIndex))
- {
- indices.push_back(keyTimeIndex);
- }
- }
- if (m_pKeyTimeSet)
- {
- m_pKeyTimeSet->MoveKeyTimes(int(indices.size()), &indices[0], scale, offset, m_copyKeyTimes);
- }
- }
- //////////////////////////////////////////////////////////////////////////
- void TimelineWidget::SelectKeysInRange(float start, float end, bool select)
- {
- for (int keyTimeIndex = 0; m_pKeyTimeSet && keyTimeIndex < m_pKeyTimeSet->GetKeyTimeCount(); ++keyTimeIndex)
- {
- float time = (m_pKeyTimeSet ? m_pKeyTimeSet->GetKeyTime(keyTimeIndex) : 0.0f);
- if (m_pKeyTimeSet && time >= start && time <= end)
- {
- m_pKeyTimeSet->SetKeyTimeSelected(keyTimeIndex, select);
- }
- }
- }
- //////////////////////////////////////////////////////////////////////////
- void TimelineWidget::SetMarkerStyle(MarkerStyle markerStyle)
- {
- m_markerStyle = markerStyle;
- }
- //////////////////////////////////////////////////////////////////////////
- void TimelineWidget::SetFPS(float fps)
- {
- m_fps = fps;
- }
- //////////////////////////////////////////////////////////////////////////
- void TimelineWidget::DrawSecondTicks(QPainter* painter)
- {
- const QPen ltgray(QColor(110, 110, 110));
- const QPen black(palette().color(QPalette::Normal, QPalette::Text));
- for (int gx = m_grid.firstGridLine.x(); gx < m_grid.firstGridLine.x() + m_grid.numGridLines.x() + 1; gx++)
- {
- painter->setPen(ltgray);
- int x = m_grid.GetGridLineX(gx);
- if (x < 0)
- {
- continue;
- }
- painter->drawLine(m_rcTimeline.left() + x, m_rcTimeline.bottom() - 2, m_rcTimeline.left() + x, m_rcTimeline.bottom() - 4);
- //if (gx % 10 == 0)
- {
- float t = m_grid.GetGridLineXValue(gx);
- t = floor(t * 1000.0f + 0.5f) / 1000.0f;
- const QString str = QString::number(t * m_fTicksTextScale, 'g');
- //t = t / pow(10,precision);
- painter->setPen(black);
- painter->drawLine(m_rcTimeline.left() + x, m_rcTimeline.bottom() - 2, m_rcTimeline.left() + x, m_rcTimeline.bottom() - 14);
- painter->drawText(m_rcTimeline.left() + x + 2, m_rcTimeline.top(), str);
- }
- }
- }
- //////////////////////////////////////////////////////////////////////////
- class TickDrawer
- {
- public:
- TickDrawer(QPainter* painter, const QRect& rect)
- : rect(rect)
- , painter(painter)
- {
- }
- void operator()(int frameIndex, int x)
- {
- if (painter)
- {
- painter->setPen(QColor(110, 110, 110));
- painter->drawLine(rect.left() + x, rect.bottom() - 2, rect.left() + x, rect.bottom() - 4);
- {
- const QString str = QString::number(frameIndex);
- painter->setPen(Qt::black);
- painter->drawLine(rect.left() + x, rect.bottom() - 2, rect.left() + x, rect.bottom() - 14);
- painter->drawText(rect.left() + x + 2, rect.top(), str);
- }
- }
- }
- QRect rect;
- QPainter* painter;
- };
- void TimelineWidget::DrawFrameTicks(QPainter* painter)
- {
- TickDrawer tickDrawer(painter, m_rcTimeline);
- GridUtils::IterateGrid(tickDrawer, 50.0f, m_grid.zoom.GetX(), m_grid.origin.GetX(), m_fps, m_grid.rect.left(), m_grid.rect.right() + 1);
- }
- void TimelineWidget::SetPlayCallback(const std::function<void()>& callback)
- {
- m_playCallback = callback;
- }
- #include <Controls/moc_TimelineCtrl.cpp>
|