R8: finish app/ pure C ABI migration (P3-P9) and make OTIO required

- app/ no longer includes engine C++ headers nor holds engine C++ types:
  engine access goes through the oakengine C ABI plus C++ wrappers
  (oakutil/oaknode.h, oakutil/oakvideo.h) and app-local mirror types
  (tooltypes, trackreferencehandle, timelinecommonapp, keyframetypes,
  subtitleapp, serializedlayoutinfoapp, nodevaluehandle, sliderdisplaytypeapp)
- engine: new C ABI functions for block/track/clip/transition navigation
  and predicates, links, caches, waveform/playback, disk folder,
  sequence_track_list, node_free, footage_is_valid, block_get_track,
  get_brush; loadotio/saveotio ported to the current engine API
- OTIO is now a required dependency: CI and CD build it on every
  platform, FindOpenTimelineIO fixed for OTIO 0.16/0.19 (the old deps
  include requirement silently disabled OTIO everywhere), runtime
  libraries are bundled into packages and copied next to macOS binaries
  (oak_copy_otio_runtime)
- fix ProjectViewModel drag&drop mime read/write size mismatch (segfault)
- unify color label naming (k_olive -> "Oak") in the app-side mirror
- docs: OTIO required, FFmpeg minimum corrected to 6.0 (en/zh)
- gtest suite: 1925 passed, 0 failed
This commit is contained in:
2026-07-31 22:46:52 +08:00
parent 18aed979a2
commit 66d761b4b7
285 changed files with 13261 additions and 5895 deletions
+32 -55
View File
@@ -31,110 +31,81 @@
namespace olive
{
class TimelineMarker;
class TimelineMarkerList;
using olive::core::Rational;
using olive::core::TimeRange;
/**
* @brief Facade accessors for marker pointers held by the ruler/scrollbar
* @brief Facade accessors for marker handles held by the ruler/scrollbar
* widgets.
*
* The widgets keep olive::TimelineMarker* / olive::TimelineMarkerList* as
* opaque identity pointers (selection, drawing, hit-testing). All engine
* data and mutations go through the liboakengine C ABI
* (oakengine/timeline.h); the pointer itself is only a handle. Marker
* times are rational seconds (num/den pairs), not frame timestamps.
* The widgets keep OakEngineMarker* / OakEngineMarkerList* as opaque
* identity handles (selection, drawing, hit-testing). All engine data and
* mutations go through the liboakengine C ABI (oakengine/timeline.h); the
* handle itself is only an identity. Marker times are rational seconds
* (num/den pairs), not frame timestamps.
*/
inline OakEngineMarker *markerhandle(TimelineMarker *marker)
{
return reinterpret_cast<OakEngineMarker *>(marker);
}
inline const OakEngineMarker *markerhandle(const TimelineMarker *marker)
{
return reinterpret_cast<const OakEngineMarker *>(marker);
}
inline TimelineMarker *markerhandle(OakEngineMarker *marker)
{
return reinterpret_cast<TimelineMarker *>(marker);
}
inline OakEngineMarkerList *markerlisthandle(TimelineMarkerList *list)
{
return reinterpret_cast<OakEngineMarkerList *>(list);
}
inline const OakEngineMarkerList *markerlisthandle(
const TimelineMarkerList *list)
{
return reinterpret_cast<const OakEngineMarkerList *>(list);
}
inline TimeRange marker_time(const TimelineMarker *marker)
inline TimeRange marker_time(const OakEngineMarker *marker)
{
int64_t in_num = 0, in_den = 1, out_num = 0, out_den = 1;
oakengine_marker_get_time(markerhandle(marker), &in_num, &in_den,
oakengine_marker_get_time(marker, &in_num, &in_den,
&out_num, &out_den);
return TimeRange(Rational(int(in_num), int(in_den)),
Rational(int(out_num), int(out_den)));
}
inline QString marker_name(const TimelineMarker *marker)
inline QString marker_name(const OakEngineMarker *marker)
{
const int size =
oakengine_marker_get_name(markerhandle(marker), nullptr, 0);
oakengine_marker_get_name(marker, nullptr, 0);
QByteArray buf(size + 1, '\0');
oakengine_marker_get_name(markerhandle(marker), buf.data(),
oakengine_marker_get_name(marker, buf.data(),
int(buf.size()));
return QString::fromUtf8(buf.constData());
}
inline int marker_color(const TimelineMarker *marker)
inline int marker_color(const OakEngineMarker *marker)
{
return oakengine_marker_get_color(markerhandle(marker));
return oakengine_marker_get_color(marker);
}
inline bool marker_has_sibling_at_time(const TimelineMarker *marker,
inline bool marker_has_sibling_at_time(const OakEngineMarker *marker,
const Rational &time)
{
return oakengine_marker_has_sibling_at_time(
markerhandle(marker), time.numerator(), time.denominator()) != 0;
marker, time.numerator(), time.denominator()) != 0;
}
inline void marker_set_time_live(TimelineMarker *marker,
inline void marker_set_time_live(OakEngineMarker *marker,
const TimeRange &range)
{
oakengine_marker_set_time_live(
markerhandle(marker), range.in().numerator(), range.in().denominator(),
marker, range.in().numerator(), range.in().denominator(),
range.out().numerator(), range.out().denominator());
}
/**
* @brief ADL customization points for
* TimeBasedViewSelectionManager<TimelineMarker>.
* TimeBasedViewSelectionManager<OakEngineMarker>.
*
* The selection manager template calls these unqualified; these overloads
* route marker access through the facade (see
* widget/keyframeview/keyframehandle.h for the NodeKeyframe equivalent).
* selection_time_target_parent() is intentionally not overloaded: the
* generic template in timebasedviewselectionmanager.h works for markers
* and marker drags never pass a time target.
* widget/keyframeview/keyframehandle.h for the keyframe equivalent).
* Markers drag both their in and out points, hence selection_time_end().
* selection_time_target_parent() returns nullptr: marker drags never pass
* a time target.
*/
inline Rational selection_time(TimelineMarker *marker)
inline Rational selection_time(OakEngineMarker *marker)
{
return marker_time(marker).in();
}
inline Rational selection_time_end(TimelineMarker *marker)
inline Rational selection_time_end(OakEngineMarker *marker)
{
return marker_time(marker).out();
}
inline void selection_set_time(TimelineMarker *marker, const Rational &time)
inline void selection_set_time(OakEngineMarker *marker, const Rational &time)
{
// Move the in-point keeping the range length (was
// TimelineMarker::set_time(const Rational &))
@@ -143,12 +114,18 @@ inline void selection_set_time(TimelineMarker *marker, const Rational &time)
marker_set_time_live(marker, TimeRange(time, time + length));
}
inline bool selection_has_sibling_at_time(TimelineMarker *marker,
inline bool selection_has_sibling_at_time(OakEngineMarker *marker,
const Rational &time)
{
return marker_has_sibling_at_time(marker, time);
}
inline OakEngineNode *selection_time_target_parent(OakEngineMarker *marker)
{
Q_UNUSED(marker)
return nullptr;
}
} // namespace olive
#endif // OAK_MARKERHANDLE_H
+3 -3
View File
@@ -49,7 +49,7 @@ QRect draw(QPainter *p, const QPoint &pt, int max_right, double scale,
int half_width = marker_width / 2;
QColor c = QtUtils::to_q_color(ColorCoding::get_color(color));
QColor c = QtUtils::to_q_color(AppColorCoding::get_color(color));
if (selected) {
p->setPen(Qt::white);
p->setBrush(c.lighter());
@@ -70,8 +70,8 @@ QRect draw(QPainter *p, const QPoint &pt, int max_right, double scale,
p->drawRect(marker_rect);
if (!name.isEmpty()) {
p->setPen(ColorCoding::get_ui_selector_color(
ColorCoding::get_color(color)));
p->setPen(AppColorCoding::get_ui_selector_color(
AppColorCoding::get_color(color)));
p->drawText(marker_rect.adjusted(marker_width / 4, 0, 0, 0), name,
op);
}
+151 -97
View File
@@ -27,17 +27,17 @@
#include <QtMath>
#include "oakutil/qtutils.h"
#include "oakutil/oaknode.h"
#include "oakutil/range.h"
#include "core.h"
#include "dialog/markerproperties/markerpropertiesdialog.h"
#include "markerhandle.h"
#include "markerpainting.h"
#include "node/project/sequence/sequence.h"
#include "node/project/serializer/serializer.h"
#include "oakengine/node.h"
#include "oakengine/serializer.h"
#include "oakengine/timeline.h"
#include "oakengine/viewer.h"
#include "oakengine/undo.h"
#include "timeline/timelineundoworkarea.h"
#include "widget/colorlabelmenu/colorlabelmenu.h"
#include "widget/menu/menushared.h"
#include "widget/timebased/timebasedwidget.h"
@@ -76,7 +76,7 @@ SeekableWidget::SeekableWidget(QWidget *parent)
set_is_timeline_axes(true);
}
void SeekableWidget::set_markers(TimelineMarkerList *markers)
void SeekableWidget::set_markers(OakEngineMarkerList *markers)
{
// Unsubscribe old marker list events via bridge
for (int64_t id : marker_list_subs_) {
@@ -91,15 +91,15 @@ void SeekableWidget::set_markers(TimelineMarkerList *markers)
markers_ = markers;
if (markers_) {
// Subscribe to marker list events via bridge instead of direct TimelineMarkerList signals
// Subscribe to marker list events via bridge instead of direct marker list signals
marker_list_subs_.append(bridge_->subscribe(
reinterpret_cast<OakEngineMarkerList *>(markers_),
markers_,
OAKENGINE_EVENT_MARKER_LIST_MARKER_ADDED));
marker_list_subs_.append(bridge_->subscribe(
reinterpret_cast<OakEngineMarkerList *>(markers_),
markers_,
OAKENGINE_EVENT_MARKER_LIST_MARKER_REMOVED));
marker_list_subs_.append(bridge_->subscribe(
reinterpret_cast<OakEngineMarkerList *>(markers_),
markers_,
OAKENGINE_EVENT_MARKER_LIST_MARKER_MODIFIED));
// Wire the bridge signals once — bridge_ outlives individual marker
@@ -125,7 +125,7 @@ void SeekableWidget::set_markers(TimelineMarkerList *markers)
viewport()->update();
}
void SeekableWidget::set_work_area(TimelineWorkArea *workarea)
void SeekableWidget::set_work_area(OakEngineWorkarea *workarea)
{
if (workarea_) {
selection_manager_.clear_selection();
@@ -166,31 +166,27 @@ void SeekableWidget::delete_selected()
return;
}
if (Sequence *sequence =
dynamic_cast<Sequence *>(get_viewer_node())) {
OakEngineNode *viewer_node =
get_viewer_node();
if (oakengine_node_is_sequence(viewer_node)) {
// Batch removal through the liboakengine C ABI facade (one
// undoable command). The facade family only wraps sequences;
// markers of other viewer nodes (e.g. footage viewers) keep
// the old per-marker command path below.
QVector<int64_t> times;
times.reserve(int(selected.size()));
for (TimelineMarker *marker : selected) {
for (OakEngineMarker *marker : selected) {
times.append(Timecode::time_to_timestamp(
marker->time().in(), timebase(), Timecode::k_round));
marker_time(marker).in(), timebase(), Timecode::k_round));
}
oakengine_sequence_marker_remove_many(
reinterpret_cast<OakEngineSequence *>(sequence),
reinterpret_cast<OakEngineSequence *>(viewer_node),
times.constData(), times.size());
return;
}
QVector<OakEngineMarker *> oak_markers;
foreach (TimelineMarker *marker, selected) {
oak_markers.append(
reinterpret_cast<OakEngineMarker *>(marker));
}
// Remove each marker (undoable individually)
for (auto *m : oak_markers) {
for (auto *m : selected) {
oakengine_marker_remove(m);
}
}
@@ -203,7 +199,7 @@ bool SeekableWidget::copy_selected(bool cut)
std::vector<const OakEngineMarker *> markers;
markers.reserve(selected.size());
for (auto *m : selected) {
markers.push_back(reinterpret_cast<OakEngineMarker *>(m));
markers.push_back(m);
}
OakEngineClipboard *cb = oakengine_clipboard_create(
@@ -225,52 +221,52 @@ bool SeekableWidget::copy_selected(bool cut)
bool SeekableWidget::paste_markers()
{
OakEngineNode *viewer =
get_viewer_node();
OakEngineProject *project = oak::Node(viewer).project().handle();
OakEngineClipboard *cb = oakengine_clipboard_create(
OAKENGINE_CLIPBOARD_MARKERS,
reinterpret_cast<OakEngineProject *>(get_viewer_node()->project()),
nullptr);
OAKENGINE_CLIPBOARD_MARKERS, project, nullptr);
int result_code;
oakengine_clipboard_paste(
cb, OAKENGINE_CLIPBOARD_MARKERS,
reinterpret_cast<OakEngineProject *>(get_viewer_node()->project()),
&result_code, nullptr, 0);
cb, OAKENGINE_CLIPBOARD_MARKERS, project, &result_code, nullptr, 0);
if (result_code == OAKENGINE_OK) {
int count = oakengine_clipboard_get_loaded_marker_count(cb);
if (count > 0) {
// Collect the pasted markers
std::vector<TimelineMarker *> markers;
std::vector<OakEngineMarker *> markers;
markers.reserve(count);
for (int i = 0; i < count; i++) {
markers.push_back(reinterpret_cast<TimelineMarker *>(
oakengine_clipboard_get_loaded_marker_at(cb, i)));
markers.push_back(
oakengine_clipboard_get_loaded_marker_at(cb, i));
}
// Normalize markers to start at playhead
Rational min = RATIONAL_MAX;
for (auto it = markers.cbegin(); it != markers.cend(); it++) {
min = std::min(min, (*it)->time().in());
min = std::min(min, marker_time(*it).in());
}
min -= get_viewer_node()->get_playhead();
int64_t ph_num = 0, ph_den = 1;
oakengine_viewer_get_playhead(viewer, &ph_num, &ph_den);
min -= Rational(int(ph_num), int(ph_den));
for (auto it = markers.cbegin(); it != markers.cend(); it++) {
TimelineMarker *m = *it;
OakEngineMarker *m = *it;
Rational new_in = m->time().in() - min;
Rational new_in = marker_time(m).in() - min;
oakengine_marker_set_time_live(
reinterpret_cast<OakEngineMarker *>(m),
m,
new_in.numerator(), new_in.denominator(),
new_in.numerator(), new_in.denominator());
if (TimelineMarker *existing =
markers_->get_marker_at_time(m->time().in())) {
oakengine_marker_remove(
reinterpret_cast<OakEngineMarker *>(existing));
if (OakEngineMarker *existing =
oakengine_marker_list_marker_at_time(
markers_, new_in.numerator(),
new_in.denominator())) {
oakengine_marker_remove(existing);
}
// Re-add the clipboard marker to the list (undoable)
oakengine_marker_list_add_existing(
reinterpret_cast<OakEngineMarkerList *>(markers_),
reinterpret_cast<OakEngineMarker *>(m));
oakengine_marker_list_add_existing(markers_, m);
}
oakengine_clipboard_free(cb);
@@ -284,7 +280,7 @@ bool SeekableWidget::paste_markers()
void SeekableWidget::mousePressEvent(QMouseEvent *event)
{
TimelineMarker *initial;
OakEngineMarker *initial;
if (hand_press(event)) {
return;
@@ -298,8 +294,12 @@ void SeekableWidget::mousePressEvent(QMouseEvent *event)
if (!(event->modifiers() & Qt::ShiftModifier)) {
selection_manager_.clear_selection();
}
if (TimelineMarker *m = dynamic_cast<TimelineMarker *>(resize_item_)) {
selection_manager_.select(m);
// resize_item_ is either the workarea or a marker (see
// find_resize_handle); the handles are opaque here, so tell
// them apart by comparing against workarea_ instead of dynamic_cast.
if (resize_item_ != workarea_) {
selection_manager_.select(
static_cast<OakEngineMarker *>(resize_item_));
}
dragging_ = true;
resize_start_ = mapToScene(event->pos());
@@ -405,19 +405,23 @@ void SeekableWidget::draw_markers(QPainter *p, int marker_bottom)
selection_manager_.clear_drawn_objects();
// Draw markers
if (markers_ && !markers_->empty() && marker_bottom > 0) {
const int marker_count =
markers_ ? oakengine_marker_list_count(markers_) : 0;
if (marker_count > 0 && marker_bottom > 0) {
int lim_left = get_left_limit();
int lim_right = get_right_limit();
for (auto it = markers_->cbegin(); it != markers_->cend(); it++) {
TimelineMarker *marker = *it;
for (int i = 0; i < marker_count; i++) {
OakEngineMarker *marker =
oakengine_marker_list_at(markers_, i);
const TimeRange range = marker_time(marker);
int marker_right = time_to_scene(marker->time().out());
int marker_right = time_to_scene(range.out());
if (marker_right < lim_left) {
continue;
}
int marker_left = time_to_scene(marker->time().in());
int marker_left = time_to_scene(range.in());
if (marker_left >= lim_right) {
break;
}
@@ -425,20 +429,20 @@ void SeekableWidget::draw_markers(QPainter *p, int marker_bottom)
int max_marker_right = lim_right;
{
// Check if there's a marker next
auto next = it;
next++;
if (next != markers_->cend()) {
if (i + 1 < marker_count) {
OakEngineMarker *next =
oakengine_marker_list_at(markers_, i + 1);
max_marker_right =
std::min(max_marker_right,
int(time_to_scene((*next)->time().in())));
int(time_to_scene(marker_time(next).in())));
}
}
QRect marker_rect = MarkerPainting::draw(
p, QPoint(marker_left, marker_bottom), max_marker_right,
get_scale(), selection_manager_.is_selected(marker),
marker->name(), marker->color(),
marker->time().in(), marker->time().out());
marker_name(marker), marker_color(marker),
range.in(), range.out());
marker_top_ = marker_rect.top();
selection_manager_.declare_drawn_object(marker, marker_rect);
}
@@ -449,19 +453,33 @@ void SeekableWidget::draw_markers(QPainter *p, int marker_bottom)
void SeekableWidget::draw_work_area(QPainter *p)
{
// Fetch workarea state through the C ABI (opaque handle on this side)
int64_t wa_in_num = 0, wa_in_den = 1, wa_out_num = 0, wa_out_den = 1;
int wa_enabled = 0;
const bool have_workarea =
workarea_ &&
oakengine_workarea_get(
workarea_, &wa_in_num,
&wa_in_den, &wa_out_num, &wa_out_den,
&wa_enabled) == OAKENGINE_OK &&
wa_enabled;
// Draw in/out workarea
if (workarea_ && workarea_->enabled()) {
if (have_workarea) {
const Rational wa_in{int(wa_in_num), int(wa_in_den)};
const Rational wa_out{int(wa_out_num), int(wa_out_den)};
int lim_left = get_left_limit();
int lim_right = get_right_limit();
int workarea_left = qMax(qreal(lim_left), time_to_scene(workarea_->in()));
int workarea_left = qMax(qreal(lim_left), time_to_scene(wa_in));
int workarea_right;
if (workarea_->out() == RATIONAL_MAX) {
if (wa_out == RATIONAL_MAX) {
workarea_right = lim_right;
} else {
workarea_right =
qMin(qreal(lim_right), time_to_scene(workarea_->out()));
qMin(qreal(lim_right), time_to_scene(wa_out));
}
QColor translucent_highlight = palette().highlight().color();
@@ -481,9 +499,9 @@ void SeekableWidget::deselect_all_markers()
void SeekableWidget::set_marker_color(int c)
{
QVector<OakEngineMarker *> oak_markers;
foreach (TimelineMarker *marker,
foreach (OakEngineMarker *marker,
selection_manager_.get_selected_objects()) {
oak_markers.append(reinterpret_cast<OakEngineMarker *>(marker));
oak_markers.append(marker);
}
oakengine_marker_set_properties(
oak_markers.data(), oak_markers.size(), c, nullptr, 0, 0, 0, 0, 0,
@@ -523,11 +541,15 @@ void SeekableWidget::seek_to_scene_point(qreal scene)
playhead_time += movement;
}
ViewerOutput *viewer = get_viewer_node();
if (viewer && playhead_time != viewer->get_playhead()) {
OakEngineNode *viewer =
get_viewer_node();
int64_t ph_num = 0, ph_den = 1;
oakengine_viewer_get_playhead(viewer, &ph_num, &ph_den);
if (viewer &&
playhead_time != Rational(int(ph_num), int(ph_den))) {
oakengine_viewer_set_playhead(
reinterpret_cast<OakEngineNode *>(viewer),
playhead_time.numerator(), playhead_time.denominator());
viewer,
playhead_time.numerator(), playhead_time.denominator());
}
}
@@ -634,39 +656,58 @@ bool SeekableWidget::find_resize_handle(QMouseEvent *event)
Rational min = scene_to_time_no_grid(scene.x() - border);
Rational max = scene_to_time_no_grid(scene.x() + border);
// Test for workarea
if (workarea_ && workarea_->enabled()) {
if (workarea_->in() >= min && workarea_->in() < max) {
// Test for workarea (state fetched through the C ABI; the handle is
// opaque on this side)
int64_t wa_in_num = 0, wa_in_den = 1, wa_out_num = 0, wa_out_den = 1;
int wa_enabled = 0;
const bool have_workarea =
workarea_ &&
oakengine_workarea_get(
workarea_, &wa_in_num,
&wa_in_den, &wa_out_num, &wa_out_den,
&wa_enabled) == OAKENGINE_OK &&
wa_enabled;
if (have_workarea) {
const Rational wa_in{int(wa_in_num), int(wa_in_den)};
const Rational wa_out{int(wa_out_num), int(wa_out_den)};
if (wa_in >= min && wa_in < max) {
resize_mode_ = k_resize_in;
} else if (workarea_->out() >= min && workarea_->out() < max) {
} else if (wa_out >= min && wa_out < max) {
resize_mode_ = k_resize_out;
}
}
if (resize_mode_ != k_resize_none) {
if (workarea_) {
if (resize_mode_ != k_resize_none) {
resize_item_ = workarea_;
resize_item_range_ = workarea_->range();
resize_item_range_ = TimeRange(wa_in, wa_out);
resize_snap_mask_ = TimeBasedWidget::k_snap_all &
~TimeBasedWidget::k_snap_to_workarea;
}
} else if (event->pos().y() >= marker_top_ &&
event->pos().y() < marker_bottom_) {
}
if (resize_mode_ == k_resize_none &&
event->pos().y() >= marker_top_ &&
event->pos().y() < marker_bottom_) {
if (markers_) {
// Check for markers
for (auto it = markers_->cbegin(); it != markers_->cend(); it++) {
TimelineMarker *m = *it;
if (m->time().in() != m->time().out()) {
if (m->time().in() >= min && m->time().in() < max) {
const int marker_count =
oakengine_marker_list_count(markers_);
for (int i = 0; i < marker_count; i++) {
OakEngineMarker *m =
oakengine_marker_list_at(markers_, i);
const TimeRange m_time = marker_time(m);
if (m_time.in() != m_time.out()) {
if (m_time.in() >= min && m_time.in() < max) {
resize_mode_ = k_resize_in;
} else if (m->time().out() >= min &&
m->time().out() < max) {
} else if (m_time.out() >= min &&
m_time.out() < max) {
resize_mode_ = k_resize_out;
}
if (resize_mode_ != k_resize_none) {
resize_item_ = m;
resize_item_range_ = m->time();
resize_item_range_ = m_time;
resize_snap_mask_ = TimeBasedWidget::k_snap_all;
break;
}
@@ -716,10 +757,16 @@ void SeekableWidget::drag_resize_handle(const QPointF &scene)
// Markers should not have the same time as anything else
// NOTE: This code is largely duplicated from TimeBasedViewSelectionManager::DragMove. Not ideal,
// but I'm not sure if there's a good way to re-use that code
if (TimelineMarker *marker =
dynamic_cast<TimelineMarker *>(resize_item_)) {
// resize_item_ is either the workarea or a marker; the handles are
// opaque here, so compare against workarea_ instead of
// dynamic_cast.
if (resize_item_ != workarea_) {
OakEngineMarker *marker =
static_cast<OakEngineMarker *>(resize_item_);
if (markers_ &&
markers_->get_marker_at_time(proposed_time) != marker) {
oakengine_marker_list_marker_at_time(
markers_, proposed_time.numerator(),
proposed_time.denominator()) != marker) {
proposed_time = presnap_time;
if (get_snap_service()) {
@@ -728,8 +775,12 @@ void SeekableWidget::drag_resize_handle(const QPointF &scene)
}
while (markers_ &&
markers_->get_marker_at_time(proposed_time) != marker &&
markers_->get_marker_at_time(proposed_time)) {
oakengine_marker_list_marker_at_time(
markers_, proposed_time.numerator(),
proposed_time.denominator()) != marker &&
oakengine_marker_list_marker_at_time(
markers_, proposed_time.numerator(),
proposed_time.denominator())) {
proposed_time += Rational(1, 1000);
}
}
@@ -739,15 +790,14 @@ void SeekableWidget::drag_resize_handle(const QPointF &scene)
new_range.set_out(proposed_time);
}
if (TimelineMarker *marker = dynamic_cast<TimelineMarker *>(resize_item_)) {
if (resize_item_ != workarea_) {
oakengine_marker_set_time_live(
reinterpret_cast<OakEngineMarker *>(marker),
static_cast<OakEngineMarker *>(resize_item_),
new_range.in().numerator(), new_range.in().denominator(),
new_range.out().numerator(), new_range.out().denominator());
} else if (TimelineWorkArea *workarea =
dynamic_cast<TimelineWorkArea *>(resize_item_)) {
} else {
oakengine_workarea_set_range(
reinterpret_cast<OakEngineWorkarea *>(workarea),
workarea_,
new_range.in().numerator(), new_range.in().denominator(),
new_range.out().numerator(), new_range.out().denominator());
}
@@ -755,16 +805,20 @@ void SeekableWidget::drag_resize_handle(const QPointF &scene)
void SeekableWidget::commit_resize_handle()
{
if (TimelineMarker *marker = dynamic_cast<TimelineMarker *>(resize_item_)) {
// resize_item_ is either the workarea or a marker (see
// find_resize_handle); compare against workarea_ instead of
// dynamic_cast since the handles are opaque here.
if (resize_item_ != workarea_) {
OakEngineMarker *marker =
static_cast<OakEngineMarker *>(resize_item_);
oakengine_marker_set_properties(
reinterpret_cast<OakEngineMarker **>(&marker), 1, -1, nullptr, 1,
&marker, 1, -1, nullptr, 1,
resize_item_range_.in().numerator(),
resize_item_range_.in().denominator(),
resize_item_range_.out().numerator(),
resize_item_range_.out().denominator(),
nullptr);
} else if (TimelineWorkArea *workarea =
dynamic_cast<TimelineWorkArea *>(resize_item_)) {
} else {
void *wa_cmd = oakengine_undo_command_create(
tr("Changed Workarea Length").toUtf8().constData(),
nullptr, nullptr, nullptr, nullptr);
+9 -9
View File
@@ -42,17 +42,17 @@ public:
return horizontalScrollBar()->value();
}
TimelineMarkerList *get_markers() const
OakEngineMarkerList *get_markers() const
{
return markers_;
}
TimelineWorkArea *get_work_area() const
OakEngineWorkarea *get_work_area() const
{
return workarea_;
}
void set_markers(TimelineMarkerList *markers);
void set_work_area(TimelineWorkArea *workarea);
void set_markers(OakEngineMarkerList *markers);
void set_work_area(OakEngineWorkarea *workarea);
virtual bool is_dragging_playhead() const override
{
@@ -83,7 +83,7 @@ public:
return !selection_manager_.get_selected_objects().empty();
}
const std::vector<TimelineMarker *> &get_selected_markers() const
const std::vector<OakEngineMarker *> &get_selected_markers() const
{
return selection_manager_.get_selected_objects();
}
@@ -146,8 +146,8 @@ private:
void commit_resize_handle();
TimelineMarkerList *markers_;
TimelineWorkArea *workarea_;
OakEngineMarkerList *markers_;
OakEngineWorkarea *workarea_;
int text_height_;
@@ -157,9 +157,9 @@ private:
bool ignore_next_focus_out_;
TimeBasedViewSelectionManager<TimelineMarker> selection_manager_;
TimeBasedViewSelectionManager<OakEngineMarker> selection_manager_;
QObject *resize_item_;
void *resize_item_;
ResizeMode resize_mode_;
TimeRange resize_item_range_;
QPointF resize_start_;
+61 -12
View File
@@ -31,6 +31,7 @@
#include "markerpainting.h"
#include "widget/menu/menu.h"
#include "widget/menu/menushared.h"
#include "widget/viewer/vieweroutpututils.h"
namespace olive
{
@@ -91,7 +92,7 @@ void TimeRuler::set_centered_text(bool c)
update();
}
void TimeRuler::set_playback_cache(PlaybackCache *cache)
void TimeRuler::set_playback_cache(void *cache)
{
if (!show_cache_status_) {
return;
@@ -108,11 +109,9 @@ void TimeRuler::set_playback_cache(PlaybackCache *cache)
if (playback_cache_) {
cache_sub_invalidated_ = bridge_->subscribe(
reinterpret_cast<void *>(playback_cache_),
OAKENGINE_EVENT_PLAYBACK_CACHE_INVALIDATED);
playback_cache_, OAKENGINE_EVENT_PLAYBACK_CACHE_INVALIDATED);
cache_sub_validated_ = bridge_->subscribe(
reinterpret_cast<void *>(playback_cache_),
OAKENGINE_EVENT_PLAYBACK_CACHE_VALIDATED);
playback_cache_, OAKENGINE_EVENT_PLAYBACK_CACHE_VALIDATED);
}
update();
@@ -285,25 +284,75 @@ void TimeRuler::drawForeground(QPainter *p, const QRectF &rect)
// If cache status is enabled
if (show_cache_status_ && playback_cache_ &&
playback_cache_->has_validated_ranges()) {
oakengine_playback_cache_has_validated_ranges(playback_cache_)) {
// FIXME: Hardcoded to get video length, if we ever need audio length, this will have to change
int h = oakengine_playback_cache_indicator_height();
QRect cache_rect(0, height() - h, width(), h);
if (ViewerOutput *viewer =
dynamic_cast<ViewerOutput *>(playback_cache_->parent())) {
int right = time_to_scene(viewer->get_video_length());
OakEngineNode *cache_parent =
oakengine_playback_cache_parent(playback_cache_);
int64_t len_num = 0, len_den = 1;
if (oakengine_viewer_from_node(cache_parent) &&
oakengine_viewer_get_video_length(cache_parent, &len_num,
&len_den) == OAKENGINE_OK &&
len_den != 0) {
int right = time_to_scene(Rational(static_cast<int>(len_num),
static_cast<int>(len_den)));
cache_rect.setWidth(std::max(0, right));
}
if (cache_rect.width() > 0) {
playback_cache_->draw(p, scene_to_time(get_scroll()), get_scale(),
cache_rect);
// App-side equivalent of PlaybackCache::draw(): the
// oakengine_playback_cache_draw() C ABI anchors the strip at
// the painter viewport's top edge and cannot reproduce this
// bottom-anchored indicator, so the validated ranges are
// painted directly from oakengine_playback_cache_valid_ranges()
// (same fill logic as the engine's draw()).
const Rational start = scene_to_time(get_scroll());
const double scale = get_scale();
p->fillRect(cache_rect, Qt::red);
QVector<int64_t> quads(4 * 64);
int range_count;
while ((range_count = oakengine_playback_cache_valid_ranges(
static_cast<OakEnginePlaybackCache *>(
playback_cache_),
quads.data(), quads.size() / 4)) == quads.size() / 4) {
quads.resize(quads.size() * 2);
}
for (int i = 0; i < range_count; i++) {
const Rational in(static_cast<int>(quads.at(i * 4 + 0)),
static_cast<int>(quads.at(i * 4 + 1)));
const Rational out(static_cast<int>(quads.at(i * 4 + 2)),
static_cast<int>(quads.at(i * 4 + 3)));
int range_left =
cache_rect.left() + (in - start).to_double() * scale;
if (range_left >= cache_rect.right()) {
continue;
}
int range_right =
cache_rect.left() + (out - start).to_double() * scale;
if (range_right < cache_rect.left()) {
continue;
}
int adjusted_left = std::max(range_left, cache_rect.left());
int adjusted_right =
std::min(range_right, cache_rect.right());
p->fillRect(adjusted_left, cache_rect.top(),
adjusted_right - adjusted_left,
cache_rect.height(), Qt::green);
}
}
}
// Draw the playhead if it's on screen at the moment
int playhead_pos = time_to_scene(get_viewer_node()->get_playhead());
int playhead_pos = time_to_scene(viewer_output_playhead(get_viewer_node()));
p->setPen(Qt::NoPen);
p->setBrush(PLAYHEAD_COLOR);
draw_playhead(p, playhead_pos, line_bottom);
+6 -3
View File
@@ -27,7 +27,6 @@
#include "engineeventbridge.h"
#include "seekablewidget.h"
#include "render/playbackcache.h"
namespace olive
{
@@ -40,7 +39,11 @@ public:
void set_centered_text(bool c);
void set_playback_cache(PlaybackCache *cache);
/**
* @brief Opaque playback cache handle (engine PlaybackCache, accessed
* through the oakengine_playback_cache_* C ABI).
*/
void set_playback_cache(void *cache);
protected:
virtual void drawForeground(QPainter *painter, const QRectF &rect) override;
@@ -65,7 +68,7 @@ private:
bool show_cache_status_;
PlaybackCache *playback_cache_;
void *playback_cache_;
EngineEventBridge *bridge_;
int64_t cache_sub_invalidated_ = 0;