From fb459e3680f507196160d97c34d348caa36e5f32 Mon Sep 17 00:00:00 2001
From: Igor Korsukov <igor.korsukov@gmail.com>
Date: Tue, 14 May 2024 12:07:41 +0300
Subject: [PATCH] [AU4] added frame start time and end time to timeline context

---
 .../ProjectScene/clipsview/Timeline.qml       | 17 +++-
 .../view/clipsview/clipslistmodel.cpp         |  2 +-
 .../view/clipsview/timelinecontext.cpp        | 94 +++++++++++++------
 .../view/clipsview/timelinecontext.h          | 56 +++++++----
 4 files changed, 120 insertions(+), 49 deletions(-)

diff --git a/au4/src/projectscene/qml/Audacity/ProjectScene/clipsview/Timeline.qml b/au4/src/projectscene/qml/Audacity/ProjectScene/clipsview/Timeline.qml
index a041976f24..c5ccdb2659 100644
--- a/au4/src/projectscene/qml/Audacity/ProjectScene/clipsview/Timeline.qml
+++ b/au4/src/projectscene/qml/Audacity/ProjectScene/clipsview/Timeline.qml
@@ -13,6 +13,18 @@ Rectangle {
     height: 76
     color: ui.theme.backgroundPrimaryColor
 
+    //! NOTE This element must be the same width as the track wave visible area.
+    //! If this is different, than appropriate changes must be made.
+    onWidthChanged: {
+        timelineContext.onResizeFrameWidth(root.width)
+    }
+
+    Component.onCompleted: {
+        timelineContext.init(root.width)
+    }
+
+    //! ~~~ TimelineContext ~~~
+    //! NOTE See comment in TimelineContext (.h)
     function onWheel(y) {
         timelineContext.onWheel(y)
     }
@@ -28,11 +40,14 @@ Rectangle {
     TimelineContext {
         id: timelineContext
     }
+    // ~~~~~~~~~~~~~~~~~~~~~~~~~~
 
     StyledTextLabel {
         anchors.fill: parent
         anchors.leftMargin: 16
-        text: "zoom: " + timelineContext.zoom + ", offset: " + timelineContext.offset
+        text: "zoom: " + timelineContext.zoom
+              + ", frame start time: " + timelineContext.frameStartTime
+              + ", end time: " + timelineContext.frameEndTime
     }
 
     SeparatorLine { anchors.bottom: parent.bottom }
diff --git a/au4/src/projectscene/view/clipsview/clipslistmodel.cpp b/au4/src/projectscene/view/clipsview/clipslistmodel.cpp
index 8b2d3512c1..dabc1f7e3d 100644
--- a/au4/src/projectscene/view/clipsview/clipslistmodel.cpp
+++ b/au4/src/projectscene/view/clipsview/clipslistmodel.cpp
@@ -25,7 +25,7 @@ void ClipsListModel::load()
 
     if (m_context) {
         connect(m_context, &TimelineContext::zoomChanged, this, &ClipsListModel::onTimelineContextValuesChanged);
-        connect(m_context, &TimelineContext::offsetChanged, this, &ClipsListModel::onTimelineContextValuesChanged);
+        connect(m_context, &TimelineContext::frameTimeChanged, this, &ClipsListModel::onTimelineContextValuesChanged);
     }
 
     beginResetModel();
diff --git a/au4/src/projectscene/view/clipsview/timelinecontext.cpp b/au4/src/projectscene/view/clipsview/timelinecontext.cpp
index 28e6e0bb9a..536632b4e6 100644
--- a/au4/src/projectscene/view/clipsview/timelinecontext.cpp
+++ b/au4/src/projectscene/view/clipsview/timelinecontext.cpp
@@ -8,18 +8,30 @@
 
 static constexpr double ZOOM_MIN = 0.1;
 
-TimelineContext::TimelineContext(QQuickItem* parent)
-    : QQuickItem(parent)
+TimelineContext::TimelineContext(QObject* parent)
+    : QObject(parent)
 {
 }
 
+void TimelineContext::init(double frameWidth)
+{
+    m_zoom = 2.0;//{ 44100.0 / 512.0 };
+    emit zoomChanged();
+
+    m_frameStartTime = 0.0;
+    emit frameStartTimeChanged();
+    m_frameEndTime = positionToTime(frameWidth);
+    emit frameEndTimeChanged();
+    emit frameTimeChanged();
+}
+
 void TimelineContext::onWheel(double y)
 {
     Qt::KeyboardModifiers modifiers = QApplication::keyboardModifiers();
     if (modifiers.testFlag(Qt::ControlModifier)) {
         changeZoom(y < 0 ? -1 : 1);
     } else if (modifiers.testFlag(Qt::ShiftModifier)) {
-        changeOffset(y < 0 ? -1 : 1);
+        shiftFrameTime(y < 0 ? -1 : 1);
     }
 }
 
@@ -38,10 +50,21 @@ void TimelineContext::changeZoom(int direction)
     setZoom(_zoom);
 }
 
-void TimelineContext::changeOffset(int direction)
+void TimelineContext::onResizeFrameWidth(double frameWidth)
 {
-    double step = 10;
-    setOffset(offset() + (step * direction));
+    setFrameEndTime(m_frameStartTime + positionToTime(frameWidth));
+    emit frameTimeChanged();
+}
+
+void TimelineContext::shiftFrameTime(int direction)
+{
+    double step = 10.0;
+    double shift = step * direction;
+
+    setFrameStartTime(m_frameStartTime + shift);
+    setFrameEndTime(m_frameEndTime + shift);
+
+    emit frameTimeChanged();
 }
 
 void TimelineContext::onSelection(double x1, double x2)
@@ -65,35 +88,16 @@ void TimelineContext::onSelectionTime(double startTime, double endTime)
     setSelectionActive(!muse::is_zero(startTime) && !muse::is_zero(endTime));
 }
 
-qint64 TimelineContext::timeToPosition(double time) const
-{
-    double t = 0.5 + m_zoom * (time - m_offset);
-    if (t < INT64_MIN) {
-        return INT64_MIN;
-    }
-    if (t > INT64_MAX) {
-        return INT64_MAX;
-    }
-    t = floor(t);
-    return static_cast<qint64>(t);
-}
-
-double TimelineContext::positionToTime(qint64 position) const
-{
-    return m_offset + position / m_zoom;
-}
-
-double TimelineContext::offset() const
+double TimelineContext::timeToPosition(double time) const
 {
-    return m_offset;
+    double p = 0.5 + m_zoom * (time - m_frameStartTime);
+    p = std::floor(p);
+    return p;
 }
 
-void TimelineContext::setOffset(double newOffset)
+double TimelineContext::positionToTime(double position) const
 {
-    if (m_offset != newOffset) {
-        m_offset = newOffset;
-        emit offsetChanged();
-    }
+    return m_frameStartTime + position / m_zoom;
 }
 
 double TimelineContext::zoom() const
@@ -148,3 +152,31 @@ void TimelineContext::setSelectionActive(bool newSelectionActive)
     m_selectionActive = newSelectionActive;
     emit selectionActiveChanged();
 }
+
+double TimelineContext::frameStartTime() const
+{
+    return m_frameStartTime;
+}
+
+void TimelineContext::setFrameStartTime(double newFrameStartTime)
+{
+    if (qFuzzyCompare(m_frameStartTime, newFrameStartTime)) {
+        return;
+    }
+    m_frameStartTime = newFrameStartTime;
+    emit frameStartTimeChanged();
+}
+
+double TimelineContext::frameEndTime() const
+{
+    return m_frameEndTime;
+}
+
+void TimelineContext::setFrameEndTime(double newFrameEndTime)
+{
+    if (qFuzzyCompare(m_frameEndTime, newFrameEndTime)) {
+        return;
+    }
+    m_frameEndTime = newFrameEndTime;
+    emit frameEndTimeChanged();
+}
diff --git a/au4/src/projectscene/view/clipsview/timelinecontext.h b/au4/src/projectscene/view/clipsview/timelinecontext.h
index 0cbc407c02..76bb2c1023 100644
--- a/au4/src/projectscene/view/clipsview/timelinecontext.h
+++ b/au4/src/projectscene/view/clipsview/timelinecontext.h
@@ -1,12 +1,23 @@
 #pragma once
 
-#include <QQuickItem>
+#include <QObject>
 
-class TimelineContext : public QQuickItem
+//! NOTE This class does two things:
+//! 1. This is a context that is passed to other classes
+//! 2. This is a controller that interprets mouse and view resize events into context values
+//!
+//! If this class becomes more complex,
+//! or we notice that its "controller" methods are being called in unexpected places,
+//! then we should split it into two separate classes.
+
+class TimelineContext : public QObject
 {
     Q_OBJECT
 
-    Q_PROPERTY(double offset READ offset WRITE setOffset NOTIFY offsetChanged FINAL)
+    //  0 sec     visible frame          end
+    //          | ~~~~~ ~~~~ ~~~|
+    Q_PROPERTY(double frameStartTime READ frameStartTime NOTIFY frameStartTimeChanged FINAL)
+    Q_PROPERTY(double frameEndTime READ frameEndTime NOTIFY frameEndTimeChanged FINAL)
     Q_PROPERTY(double zoom READ zoom WRITE setZoom NOTIFY zoomChanged FINAL)
 
     Q_PROPERTY(double selectionStartTime READ selectionStartTime NOTIFY selectionStartTimeChanged FINAL)
@@ -15,17 +26,11 @@ class TimelineContext : public QQuickItem
 
 public:
 
-    TimelineContext(QQuickItem* parent = nullptr);
-
-    Q_INVOKABLE void onWheel(double y);
-    Q_INVOKABLE void onSelection(double x1, double x2);
-    Q_INVOKABLE void resetSelection();
+    TimelineContext(QObject* parent = nullptr);
 
-    Q_INVOKABLE qint64 timeToPosition(double time) const;
-    Q_INVOKABLE double positionToTime(qint64 position) const;
+    double frameStartTime() const;
+    double frameEndTime() const;
 
-    double offset() const;
-    void setOffset(double newOffset);
     double zoom() const;
     void setZoom(double zoom);
 
@@ -35,9 +40,23 @@ public:
     void setSelectionEndTime(double time);
     bool selectionActive() const;
 
+    Q_INVOKABLE void init(double frameWidth);
+
+    Q_INVOKABLE void onResizeFrameWidth(double frameWidth);
+    Q_INVOKABLE void onWheel(double y);
+
+    Q_INVOKABLE void onSelection(double x1, double x2);
+    Q_INVOKABLE void resetSelection();
+
+    Q_INVOKABLE double timeToPosition(double time) const;
+    Q_INVOKABLE double positionToTime(double position) const;
+
 signals:
 
-    void offsetChanged();
+    void frameStartTimeChanged();
+    void frameEndTimeChanged();
+    void frameTimeChanged(); // any or both together
+
     void zoomChanged();
 
     void selectionStartTimeChanged();
@@ -46,15 +65,20 @@ signals:
 
 private:
 
+    void shiftFrameTime(int direction);
+    void setFrameStartTime(double newFrameStartTime);
+    void setFrameEndTime(double newFrameEndTime);
+
     void changeZoom(int direction);
-    void changeOffset(int direction);
 
     void onSelectionTime(double t1, double t2);
     void updateSelectionActive();
     void setSelectionActive(bool newSelectionActive);
 
-    double m_offset = 0.0;
-    double m_zoom = 2.0;//{ 44100.0 / 512.0 };
+    double m_frameStartTime = 0.0;
+    double m_frameEndTime = 0.0;
+
+    double m_zoom = 1.0; // see init
 
     double m_selecitonStartTime = 0.0;
     double m_selectionEndTime = 0.0;
-- 
GitLab