diff options
author | Mauro Persano <mauro.persano@kdab.com> | 2017-12-12 21:19:18 -0200 |
---|---|---|
committer | Andras Mantia <andras@kdab.com> | 2017-12-20 10:56:48 +0000 |
commit | dd78a8df86ff02e95b464e120faac8e83478ca4a (patch) | |
tree | 12a543ac642bb0376fa0c4ba8b5a1e3739eea620 /src/Authoring/Studio/Palettes/Timeline | |
parent | 07cce9cbd771fdf09781570e97032af8f0d1cdf1 (diff) |
Add timeline tick marks
Port the tick mark drawing code in CTimeMeasure to a QML item
Change-Id: I75410f9d95e9c8a9aed6bb451ea9f22fa38c476b
Reviewed-by: Andras Mantia <andras@kdab.com>
Diffstat (limited to 'src/Authoring/Studio/Palettes/Timeline')
4 files changed, 402 insertions, 97 deletions
diff --git a/src/Authoring/Studio/Palettes/Timeline/TimeMeasureItem.cpp b/src/Authoring/Studio/Palettes/Timeline/TimeMeasureItem.cpp new file mode 100644 index 00000000..92b87390 --- /dev/null +++ b/src/Authoring/Studio/Palettes/Timeline/TimeMeasureItem.cpp @@ -0,0 +1,207 @@ +/**************************************************************************** +** +** Copyright (C) 2017 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt 3D Studio. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "TimeMeasureItem.h" +#include "CoreUtils.h" +#include "StudioUtils.h" +#include "StudioPreferences.h" + +#include <QPainter> + +TimeMeasureItem::TimeMeasureItem(QQuickItem *parent) + : QQuickPaintedItem(parent) +{ +} + +TimeMeasureItem::~TimeMeasureItem() +{ +} + +void TimeMeasureItem::paint(QPainter *painter) +{ + const double edgeMargin = 2; + const double largeHashOffset = 5; + const double mediumHashOffset = 6; + const double smallHashOffset = 3; + + QPen pen(CStudioPreferences::GetRulerTickColor()); + painter->setPen(pen); + + long theLength = width(); + long theHeight = height() - edgeMargin; + + double theTotalMeasure = theLength / m_Ratio; + + long theNumLargeHashes = (long)(theTotalMeasure / m_LargeHashInterval) + 1; + + long theOffset = m_Offset - (m_Offset % ::dtol(m_LargeHashInterval)); + + for (long i = 0; i < theNumLargeHashes + 1; ++i) { + double theMeasure = m_LargeHashInterval * i + theOffset; + + long thePos = ::TimeToPos(theMeasure - m_Offset, m_Ratio); + + if (thePos > 0) + painter->drawLine(thePos, theHeight, thePos, theHeight - largeHashOffset); + + DrawMeasureText(painter, thePos, long(theMeasure)); + + // sanity check + if (m_MediumHashInterval > 0) { + thePos = ::TimeToPos(theMeasure - m_Offset + m_MediumHashInterval, m_Ratio); + if (thePos > 0) + painter->drawLine(thePos, theHeight, thePos, theHeight - mediumHashOffset); + + for (double theSmallInterval = 0; theSmallInterval < m_LargeHashInterval; + theSmallInterval += m_SmallHashInterval) { + thePos = ::TimeToPos(theMeasure - m_Offset + theSmallInterval, m_Ratio); + + if (thePos > 0) + painter->drawLine(thePos, theHeight, thePos, theHeight - smallHashOffset); + } + } // if medium is valid + } + + // Draw the top outline + painter->drawLine(0, theHeight, theLength, theHeight); +} + +void TimeMeasureItem::setTimeRatio(double inTimeRatio) +{ + if (qFuzzyCompare(m_Ratio, inTimeRatio)) + return; + + m_Ratio = inTimeRatio; + + double theTimePerPixel = (double)(1 / inTimeRatio); + + // Only go through this if it has actually changed + if (theTimePerPixel != m_TimePerPixel) { + m_TimePerPixel = theTimePerPixel; + + // Go through the possible hash settings and find the one that best suits the + // time per pixel. + double theMillisPerLargeHash = theTimePerPixel * 50; + if (theMillisPerLargeHash <= 100) // 100ms + theMillisPerLargeHash = 100; + else if (theMillisPerLargeHash <= 200) // 200ms + theMillisPerLargeHash = 200; + else if (theMillisPerLargeHash <= 500) // .5s + theMillisPerLargeHash = 500; + else if (theMillisPerLargeHash <= 1000) // 1s + theMillisPerLargeHash = 1000; + else if (theMillisPerLargeHash <= 2000) // 2s + theMillisPerLargeHash = 2000; + else if (theMillisPerLargeHash <= 5000) // 5s + theMillisPerLargeHash = 5000; + else if (theMillisPerLargeHash <= 10000) // 10s + theMillisPerLargeHash = 10000; + else if (theMillisPerLargeHash <= 20000) // 20s + theMillisPerLargeHash = 20000; + else if (theMillisPerLargeHash <= 30000) // 30s + theMillisPerLargeHash = 30000; + else if (theMillisPerLargeHash <= 60000) // 1m + theMillisPerLargeHash = 60000; + else if (theMillisPerLargeHash <= 120000) // 2m + theMillisPerLargeHash = 120000; + else if (theMillisPerLargeHash <= 300000) // 5m + theMillisPerLargeHash = 300000; + else if (theMillisPerLargeHash <= 600000) // 10m + theMillisPerLargeHash = 600000; + else if (theMillisPerLargeHash <= 1200000) // 20m + theMillisPerLargeHash = 1200000; + else if (theMillisPerLargeHash <= 1800000) // 30m + theMillisPerLargeHash = 1800000; + else if (theMillisPerLargeHash <= 3600000) // 1h + theMillisPerLargeHash = 3600000; + else + theMillisPerLargeHash = 7200000; // 2h + + // Set the distances between the hashes + m_LargeHashInterval = theMillisPerLargeHash; + m_MediumHashInterval = theMillisPerLargeHash / 2; + m_SmallHashInterval = theMillisPerLargeHash / 10; + + update(); + } + + emit timeRatioChanged(inTimeRatio); +} + +double TimeMeasureItem::timeRatio() const +{ + return m_Ratio; +} + +void TimeMeasureItem::DrawMeasureText(QPainter *painter, long inPosition, long inMeasure) const +{ + QString theTimeFormat(FormatTime(inMeasure)); + + // Offset the position by half the text size to center it over the hash. + QFontMetrics fm = painter->fontMetrics(); + const auto textSize = fm.size(Qt::TextSingleLine, theTimeFormat); + inPosition -= ::dtol(textSize.width() / 2); + + QRectF rect(0, 0, width(), height()); + rect.translate(inPosition, -3); + + painter->drawText(rect, Qt::AlignLeft | Qt::AlignVCenter, theTimeFormat); +} + +QString TimeMeasureItem::FormatTime(long inTime) const +{ + long theHours = inTime / 3600000; + long theMinutes = inTime % 3600000 / 60000; + long theSeconds = inTime % 60000 / 1000; + long theMillis = inTime % 1000; + + bool theHoursOnlyFlag = theHours != 0 && theMinutes == 0 && theSeconds == 0 && theMillis == 0; + bool theMinutesOnlyFlag = + !theHoursOnlyFlag && theMinutes != 0 && theSeconds == 0 && theMillis == 0; + bool theSecondsOnlyFlag = !theMinutesOnlyFlag && theMillis == 0; + + QString theTime; + // If only hours are being displayed then format it as hours. + if (theHoursOnlyFlag) { + theTime = tr("%1h").arg(theHours); + } + // If only minutes are being displayed then format it as minutes. + else if (theMinutesOnlyFlag) { + theTime = tr("%1m").arg(theMinutes); + } + // If only seconds are being displayed then format as seconds + else if (theSecondsOnlyFlag) { + theTime = tr("%1s").arg(theSeconds); + } + // If the intervals are correct then this should only be tenths of seconds, so do that. + else { + theTime = tr("0.%1s").arg(theMillis / 100); + } + + return theTime; +} diff --git a/src/Authoring/Studio/Palettes/Timeline/TimeMeasureItem.h b/src/Authoring/Studio/Palettes/Timeline/TimeMeasureItem.h new file mode 100644 index 00000000..5304929d --- /dev/null +++ b/src/Authoring/Studio/Palettes/Timeline/TimeMeasureItem.h @@ -0,0 +1,63 @@ +/**************************************************************************** +** +** Copyright (C) 2017 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt 3D Studio. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef TIMEMEASUREITEM_H +#define TIMEMEASUREITEM_H + +#include <QQuickPaintedItem> + +class TimeMeasureItem : public QQuickPaintedItem +{ + Q_OBJECT + Q_PROPERTY(double timeRatio READ timeRatio WRITE setTimeRatio NOTIFY timeRatioChanged) + +public: + explicit TimeMeasureItem(QQuickItem *parent = nullptr); + ~TimeMeasureItem() override; + + void paint(QPainter *painter) override; + + double timeRatio() const; + void setTimeRatio(double timeRatio); + +Q_SIGNALS: + void timeRatioChanged(double timeRatio); + +private: + void DrawMeasureText(QPainter *painter, long inPosition, long inMeasure) const; + QString FormatTime(long inTime) const; + + double m_SmallHashInterval = 0.0; + double m_MediumHashInterval = 0.0; + double m_LargeHashInterval = 0.0; + double m_Ratio = 0.0; + long m_Offset = 0; + double m_TimePerPixel = 0.0; +}; + +#endif // TIMEMEASUREITEM_H diff --git a/src/Authoring/Studio/Palettes/Timeline/Timeline.qml b/src/Authoring/Studio/Palettes/Timeline/Timeline.qml index 45db8df6..4dfb0b78 100644 --- a/src/Authoring/Studio/Palettes/Timeline/Timeline.qml +++ b/src/Authoring/Studio/Palettes/Timeline/Timeline.qml @@ -29,6 +29,7 @@ import QtQuick 2.8 import QtQuick.Controls 2.2 import QtQuick.Layouts 1.3 +import Qt3DStudio 1.0 import "../controls" Rectangle { @@ -48,102 +49,117 @@ Rectangle { spacing: 5 - ListView { - id: browserList + ColumnLayout { + anchors.fill: parent + spacing: 0 Layout.minimumWidth: root.splitterPos Layout.maximumWidth: root.splitterPos - Layout.fillHeight: true - Layout.minimumHeight: 80 - Layout.preferredHeight: count * 20 Layout.preferredWidth: root.width - ScrollBar.vertical: scrollBar + Rectangle { + Layout.fillWidth: true + Layout.preferredWidth: parent.width + Layout.preferredHeight: itemHeight + color: "transparent" + } - model: _timelineView.objectModel - boundsBehavior: Flickable.StopAtBounds - clip: true - currentIndex: _timelineView.selection + ListView { + id: browserList + + Layout.fillWidth: true + Layout.fillHeight: true + Layout.minimumHeight: 80 + Layout.preferredHeight: count * itemHeight + Layout.preferredWidth: root.width + + ScrollBar.vertical: scrollBar - delegate: Rectangle { - id: delegateItem + model: _timelineView.objectModel + boundsBehavior: Flickable.StopAtBounds + clip: true + currentIndex: _timelineView.selection + + delegate: Rectangle { + id: delegateItem - width: parent.width - height: model.parentExpanded ? itemHeight : 0 + width: parent.width + height: model.parentExpanded ? itemHeight : 0 - color: model.selected ? _selectionColor : _studioColor2 - border.color: _backgroundColor + color: model.selected ? _selectionColor : _studioColor2 + border.color: _backgroundColor - visible: height > 0 + visible: height > 0 - Behavior on height { - NumberAnimation { - duration: 100 - easing.type: Easing.OutQuad + Behavior on height { + NumberAnimation { + duration: 100 + easing.type: Easing.OutQuad + } } - } - MouseArea { - id: delegateArea + MouseArea { + id: delegateArea - anchors.fill: parent - onClicked: _timelineView.select(model.index, mouse.modifiers) - } + anchors.fill: parent + onClicked: _timelineView.select(model.index, mouse.modifiers) + } - Row { - id: row + Row { + id: row - x: model.depth * 20 - anchors.verticalCenter: parent.verticalCenter - height: itemHeight - width: splitterPos - x - spacing: 5 + x: model.depth * 20 + anchors.verticalCenter: parent.verticalCenter + height: itemHeight + width: splitterPos - x + spacing: 5 - Image { - source: { - if (!model.hasChildren) + Image { + source: { + if (!model.hasChildren) return ""; - model.expanded ? _resDir + "arrow_down.png" - : _resDir + "arrow.png"; - } + model.expanded ? _resDir + "arrow_down.png" + : _resDir + "arrow.png"; + } - MouseArea { - anchors.fill: parent - onClicked: model.expanded = !model.expanded + MouseArea { + anchors.fill: parent + onClicked: model.expanded = !model.expanded + } } - } - Item { - height: itemHeight - width: typeIcon.width + name.width + 10 + Item { + height: itemHeight + width: typeIcon.width + name.width + 10 - Row { - spacing: 10 - Image { - id: typeIcon + Row { + spacing: 10 + Image { + id: typeIcon - source: model.icon - } + source: model.icon + } - StyledLabel { - id: name - anchors.verticalCenter: typeIcon.verticalCenter - color: model.textColor - text: model.name + StyledLabel { + id: name + anchors.verticalCenter: typeIcon.verticalCenter + color: model.textColor + text: model.name + } } } } } - } - onCurrentIndexChanged: _timelineView.selection = currentIndex + onCurrentIndexChanged: _timelineView.selection = currentIndex - Connections { - target: _timelineView - onSelectionChanged: { - if (browserList.currentIndex !== _timelineView.selection) + Connections { + target: _timelineView + onSelectionChanged: { + if (browserList.currentIndex !== _timelineView.selection) browserList.currentIndex = _timelineView.selection; + } } } } @@ -152,7 +168,7 @@ Rectangle { Layout.fillWidth: true Layout.fillHeight: true Layout.minimumHeight: 80 - Layout.preferredHeight: timelineItemsList.count * 20 + Layout.preferredHeight: (timelineItemsList.count + 1) * itemHeight Layout.preferredWidth: root.width contentHeight: height @@ -163,50 +179,66 @@ Rectangle { policy: ScrollBar.AlwaysOn } - ListView { - id: timelineItemsList - + ColumnLayout { anchors.fill: parent - ScrollBar.vertical: scrollBar + spacing: 0 - model: browserList.model - boundsBehavior: Flickable.StopAtBounds - clip: true - currentIndex: browserList.currentIndex + TimeMeasureItem { + Layout.fillWidth: true + Layout.preferredWidth: parent.width + Layout.preferredHeight: itemHeight + timeRatio: 0.05 + } - delegate: Rectangle { - id: timelineItemsDelegateItem + ListView { + id: timelineItemsList - width: parent.width - height: model.parentExpanded ? itemHeight : 0 + Layout.fillWidth: true + Layout.fillHeight: true + Layout.preferredHeight: count * itemHeight + Layout.preferredWidth: root.width - color: model.selected ? _selectionColor : "#404244" - border.color: _backgroundColor + ScrollBar.vertical: scrollBar - visible: height > 0 + model: browserList.model + boundsBehavior: Flickable.StopAtBounds + clip: true + currentIndex: browserList.currentIndex - MouseArea { - id: timelineItemsDelegateArea + delegate: Rectangle { + id: timelineItemsDelegateItem - anchors.fill: parent - onClicked: _timelineView.select(model.index, mouse.modifiers) - } + width: parent.width + height: model.parentExpanded ? itemHeight : 0 - TimelineItem { - height: parent.height - visible: timeInfo.endPosition > timeInfo.startPosition + color: model.selected ? _selectionColor : "#404244" + border.color: _backgroundColor - timeInfo: model.timeInfo - color: model.itemColor - borderColor: root.color - selected: model.selected - selectionColor: model.selectedColor - } + visible: height > 0 - Keyframes { - anchors.verticalCenter: parent.verticalCenter - keyframes: model.keyframes + MouseArea { + id: timelineItemsDelegateArea + + anchors.fill: parent + onClicked: _timelineView.select(model.index, mouse.modifiers) + } + + TimelineItem { + height: parent.height + visible: timeInfo.endPosition > timeInfo.startPosition + + timeInfo: model.timeInfo + color: model.itemColor + borderColor: root.color + selected: model.selected + selectionColor: model.selectedColor + } + + Keyframes { + anchors.verticalCenter: parent.verticalCenter + keyframes: model.keyframes + } } } } diff --git a/src/Authoring/Studio/Palettes/Timeline/TimelineView.cpp b/src/Authoring/Studio/Palettes/Timeline/TimelineView.cpp index 10707e21..93d1a25b 100644 --- a/src/Authoring/Studio/Palettes/Timeline/TimelineView.cpp +++ b/src/Authoring/Studio/Palettes/Timeline/TimelineView.cpp @@ -37,6 +37,8 @@ #include "StudioPreferences.h" #include "StudioUtils.h" +#include "TimeMeasureItem.h" + #include "Qt3DSDMStudioSystem.h" #include "Qt3DSDMSlides.h" #include "Qt3DSDMHandles.h" @@ -169,6 +171,7 @@ void TimelineView::initialize() , tr("Creation of TimebarTimeInfo not allowed from QML")); qmlRegisterUncreatableType<TimebarTimeInfo>("Qt3DStudio", 1, 0, "KeyframeInfo" , tr("Creation of KeyframeInfo not allowed from QML")); + qmlRegisterType<TimeMeasureItem>("Qt3DStudio", 1, 0, "TimeMeasureItem"); engine()->addImportPath(qmlImportPath()); setSource(QUrl("qrc:/Palettes/Timeline/Timeline.qml"_L1)); } |