app: PlaybackController + playhead subscription migration (issues 0c, 1-4)

New app/playback/playbackcontroller.{h,cpp}: a lazily created
process-wide singleton whose set_playhead() forwards to
oakengine_viewer_set_playhead and re-broadcasts playhead_changed as a
plain Qt signal. Every app-side oakengine_viewer_set_playhead call site
(17 across viewer, timelinewidget, timelineview, import tool,
timebasedwidget, keyframecontrol, export dialog, mainwindow) now goes
through it.

Migrate the first four playhead subscribers off the raw C event
subscription / EngineEventBridge to the controller signal:
- TimeBasedView (issue 1)
- NodeParamViewWidgetBridge (issue 2)
- NodeParamViewKeyframeControl (issue 3)
- NodeParamViewConnectedLabel (issue 4)

Each stores a QMetaObject::Connection filtered on its viewer node and
disconnects on teardown; the viewer_sub_ members are gone. Plan docs
mark issues 0c and 1-4 as done.

Closes #30, closes #31, closes #32, closes #51, closes #52.
This commit is contained in:
2026-08-05 00:01:12 +08:00
parent 712badbaa7
commit b77344b506
20 changed files with 184 additions and 101 deletions
+7 -6
View File
@@ -56,6 +56,7 @@
#include "oakengine/proxy.h"
#include "oakengine/timeline.h"
#include "oakengine/viewer.h"
#include "playback/playbackcontroller.h"
#include "common/configwrapper.h"
#include "tool/add.h"
#include "tool/beam.h"
@@ -555,7 +556,7 @@ TimelineWidget::TimelineWidget(QWidget *parent)
start = std::min(start, block_in(b));
}
if (start != RATIONAL_MAX) {
oakengine_viewer_set_playhead(
PlaybackController::instance()->set_playhead(
reinterpret_cast<OakEngineNode *>(get_connected_node()),
start.numerator(), start.denominator());
}
@@ -758,7 +759,7 @@ void TimelineWidget::ConnectNodeEvent(OakEngineNode *n)
connect(timecode_label_, &RationalSlider::value_changed, this,
[handle](const Rational &time) {
oakengine_viewer_set_playhead(
PlaybackController::instance()->set_playhead(
handle, time.numerator(), time.denominator());
});
{
@@ -1076,7 +1077,7 @@ void TimelineWidget::DeleteSelected(bool ripple)
clear_ghosts();
if (ripple && rippled && new_playhead != RATIONAL_MAX) {
oakengine_viewer_set_playhead(
PlaybackController::instance()->set_playhead(
reinterpret_cast<OakEngineNode *>(get_connected_node()),
new_playhead.numerator(), new_playhead.denominator());
}
@@ -1308,7 +1309,7 @@ void TimelineWidget::delete_in_to_out(bool ripple)
// Playhead move is not undoable and stays here (same as before).
if (ripple) {
oakengine_viewer_set_playhead(
PlaybackController::instance()->set_playhead(
reinterpret_cast<OakEngineNode *>(get_connected_node()),
wa_in.numerator(), wa_in.denominator());
}
@@ -3196,14 +3197,14 @@ void TimelineWidget::ripple_to(TimelineApp::MovementMode mode)
// If we rippled, ump to where new cut is if applicable
if (mode == TimelineApp::k_trim_in) {
oakengine_viewer_set_playhead(
PlaybackController::instance()->set_playhead(
reinterpret_cast<OakEngineNode *>(get_connected_node()),
closest_point_to_playhead.numerator(),
closest_point_to_playhead.denominator());
} else if (mode == TimelineApp::k_trim_out &&
closest_point_to_playhead ==
viewer_playhead(reinterpret_cast<OakEngineNode *>(get_connected_node()))) {
oakengine_viewer_set_playhead(
PlaybackController::instance()->set_playhead(
reinterpret_cast<OakEngineNode *>(get_connected_node()),
playhead_time.numerator(), playhead_time.denominator());
}