refactor: workspace layout — crates/, app at root, legacy C++ removed

Single mechanical restructure commit:
- root Cargo.toml = oakapp bin + workspace; one cargo build produces
  oakapp, oak-cli, oak-worker, liboakengine.dylib
- app/rust/src -> src/ (app at repo root, no rust/ nesting)
- src/<mod>/rust -> crates/oak<mod>; src/oakcore-rs -> crates/oakcore;
  src/bindings/oakotio -> crates/oakotio; src/engine/rust ->
  crates/oakengine (keeps cdylib+staticlib+rlib)
- public C headers include/<mod>/ -> crates/oakengine/include/<mod>/
- OFX SDK headers vendored into crates/oakplugin/ofx/ (HostSupport gone)
- legacy deleted: old src/ C++ modules, engine/, core/, ffmpeg_bridge/,
  app/ (Qt), cli/worker C++, root CMakeLists, third_party/KDDockWidgets
  submodule, otio-install, all build-* output (~40GB)
- oakstorage kept but excluded from the workspace (skeleton w/ todos);
  gpui excluded (own workspace)
- verified: cargo build green, cargo test --workspace 1845/0
  (with the documented OCIO_RS_* env override for the homebrew OCIO)
This commit is contained in:
2026-08-10 20:24:25 +08:00
parent f8540e3892
commit 013a175707
4212 changed files with 8331 additions and 2274987 deletions
-28
View File
@@ -1,28 +0,0 @@
# Olive - Non-Linear Video Editor
# Copyright (C) 2022 Olive Team
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
set(OLIVE_SOURCES
${OLIVE_SOURCES}
widget/timebased/timebasedview.cpp
widget/timebased/timebasedview.h
widget/timebased/timebasedviewselectionmanager.cpp
widget/timebased/timebasedviewselectionmanager.h
widget/timebased/timebasedwidget.cpp
widget/timebased/timebasedwidget.h
widget/timebased/timescaledobject.cpp
widget/timebased/timescaledobject.h
PARENT_SCOPE
)
-336
View File
@@ -1,336 +0,0 @@
/***
Olive - Non-Linear Video Editor
Copyright (C) 2022 Olive Team
Modifications Copyright (C) 2025 mikesolar
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
***/
#include "timebasedview.h"
#include "playback/playbackcontroller.h"
#include <QGraphicsRectItem>
#include <QMouseEvent>
#include <QScrollBar>
#include <QTimer>
#include "widget/timebased/timebasedwidget.h"
#include "oakengine/events.h"
#include "oakengine/viewer.h"
namespace olive
{
TimeBasedView::TimeBasedView(QWidget *parent)
: HandMovableView(parent)
, playhead_scene_left_(-1)
, playhead_scene_right_(-1)
, dragging_playhead_(false)
, snapped_(false)
, snap_service_(nullptr)
, y_axis_enabled_(false)
, y_scale_(1.0)
, viewer_(nullptr)
{
// Sets scene to our scene
setScene(&scene_);
// Set default scale (ensures non-zero scale from beginning)
set_scale(1.0);
// Default to no default drag mode
set_default_drag_mode(NoDrag);
// Signal to update bounding rect when the scene changes
connect(&scene_, &QGraphicsScene::changed, this,
&TimeBasedView::update_scene_rect);
// Workaround for Qt drawing issues with the default MinimalViewportUpdate. While this might be
// slower (Qt documentation says it may actually be faster in some situations),
// MinimalViewportUpdate causes all sorts of graphical crud building up in the scene
setViewportUpdateMode(QGraphicsView::FullViewportUpdate);
}
void TimeBasedView::TimebaseChangedEvent(const Rational &)
{
// Timebase influences position/visibility of playhead
viewport()->update();
}
void TimeBasedView::enable_snap(const std::vector<Rational> &points)
{
snapped_ = true;
snap_time_ = points;
viewport()->update();
}
void TimeBasedView::disable_snap()
{
snapped_ = false;
viewport()->update();
}
const double &TimeBasedView::get_y_scale() const
{
return y_scale_;
}
void TimeBasedView::VerticalScaleChangedEvent(double)
{
}
void TimeBasedView::zoom_into_cursor_position(QWheelEvent *event,
double scale_multiplier,
const QPointF &cursor_pos)
{
// If CTRL is held (or a preference is set to swap CTRL behavior), we zoom instead of scrolling
bool only_vertical = false;
bool only_horizontal = false;
// Ctrl+Shift limits to only one axis
// Alt switches between horizontal only (alt held) or vertical only (alt not held)
if (y_axis_enabled_) {
if (event->modifiers() & Qt::ShiftModifier) {
if (event->modifiers() & Qt::AltModifier) {
only_horizontal = true;
} else {
only_vertical = true;
}
}
} else {
only_horizontal = true;
}
if (!only_vertical) {
double old_scroll = horizontalScrollBar()->value();
double old_scale = get_scale();
emit scale_changed(old_scale * scale_multiplier);
// Use GetScale so that if this value was clamped, we don't erroneously use an unclamped value
int new_x_scroll =
qRound((cursor_pos.x() + old_scroll) / old_scale * get_scale() -
cursor_pos.x());
horizontalScrollBar()->setValue(new_x_scroll);
}
if (!only_horizontal) {
double old_y_scroll = verticalScrollBar()->value();
double old_y_scale = get_y_scale();
set_y_scale(old_y_scale * scale_multiplier);
// Use GetYScale so that if this value was clamped, we don't erroneously use an unclamped value
int new_y_scroll =
qRound((cursor_pos.y() + old_y_scroll) / old_y_scale * get_y_scale() -
cursor_pos.y());
verticalScrollBar()->setValue(new_y_scroll);
}
}
void TimeBasedView::set_y_scale(const double &y_scale)
{
Q_ASSERT(y_scale > 0);
y_scale_ = y_scale;
if (y_axis_enabled_) {
VerticalScaleChangedEvent(y_scale_);
viewport()->update();
}
}
void TimeBasedView::set_viewer_node(OakEngineNode *v)
{
disconnect(viewer_conn_);
viewer_ = v;
if (viewer_) {
viewer_conn_ = connect(
PlaybackController::instance(),
&PlaybackController::playhead_changed, this,
[this](OakEngineNode *n, const core::Rational &) {
if (n == viewer_) {
viewport()->update();
}
});
}
}
QPointF TimeBasedView::scale_point(const QPointF &p) const
{
return QPointF(p.x() * get_scale(), p.y() * get_y_scale());
}
QPointF TimeBasedView::unscale_point(const QPointF &p) const
{
return QPointF(p.x() / get_scale(), p.y() / get_y_scale());
}
void TimeBasedView::drawForeground(QPainter *painter, const QRectF &rect)
{
QGraphicsView::drawForeground(painter, rect);
if (!timebase().isNull()) {
double width = time_to_scene(timebase());
playhead_scene_left_ = get_playhead_x();
playhead_scene_right_ = playhead_scene_left_ + width;
QRectF playhead_rect(playhead_scene_left_, rect.top(), width,
rect.height());
// Get playhead highlight color
QColor highlight = palette().text().color();
highlight.setAlpha(32);
painter->setPen(Qt::NoPen);
painter->setBrush(highlight);
painter->drawRect(playhead_rect);
// FIXME: Hardcoded...
painter->setPen(PLAYHEAD_COLOR);
painter->setBrush(Qt::NoBrush);
painter->drawLine(
QLineF(playhead_rect.topLeft(), playhead_rect.bottomLeft()));
}
if (snapped_) {
painter->setPen(palette().text().color());
for (const Rational &r : snap_time_) {
double x = time_to_scene(r);
painter->drawLine(x, rect.top(), x, rect.height());
}
}
}
bool TimeBasedView::playhead_press(QMouseEvent *event)
{
QPointF scene_pos = mapToScene(event->pos());
dragging_playhead_ = (event->button() == Qt::LeftButton &&
scene_pos.x() >= playhead_scene_left_ &&
scene_pos.x() < playhead_scene_right_);
return dragging_playhead_;
}
bool TimeBasedView::playhead_move(QMouseEvent *event)
{
if (!dragging_playhead_) {
return false;
}
if (viewer_) {
QPointF scene_pos = mapToScene(event->pos());
Rational mouse_time = qMax(Rational(0), scene_to_time(scene_pos.x()));
if (Core::instance()->snapping() && snap_service_) {
Rational movement;
snap_service_->snap_point({ mouse_time }, &movement,
TimeBasedWidget::k_snap_all &
~TimeBasedWidget::k_snap_to_playhead);
mouse_time += movement;
}
oakengine_viewer_set_playhead(
reinterpret_cast<OakEngineNode *>(viewer_),
mouse_time.numerator(), mouse_time.denominator());
}
return true;
}
bool TimeBasedView::playhead_release(QMouseEvent *)
{
if (dragging_playhead_) {
dragging_playhead_ = false;
if (snap_service_) {
snap_service_->hide_snaps();
}
return true;
}
return false;
}
qreal TimeBasedView::get_playhead_x()
{
if (viewer_) {
int64_t pn, pd;
oakengine_viewer_get_playhead(
reinterpret_cast<OakEngineNode *>(viewer_), &pn, &pd);
return time_to_scene(Rational(pn, pd));
} else {
return 0;
}
}
void TimeBasedView::set_end_time(const Rational &length)
{
end_time_ = length;
update_scene_rect();
}
void TimeBasedView::update_scene_rect()
{
QRectF bounding_rect = scene_.itemsBoundingRect();
// There's no need for a timeline to ever go below 0 on the X scale
bounding_rect.setLeft(0);
// Ensure the scene is always the full length of the timeline with a gap at the end to work with
bounding_rect.setRight(time_to_scene(end_time_) + width());
// Any further rect processing from derivatives can be done here
SceneRectUpdateEvent(bounding_rect);
// If the scene is already this rect, do nothing
if (scene_.sceneRect() != bounding_rect) {
scene_.setSceneRect(bounding_rect);
}
}
void TimeBasedView::resizeEvent(QResizeEvent *event)
{
QGraphicsView::resizeEvent(event);
update_scene_rect();
}
void TimeBasedView::ScaleChangedEvent(const double &scale)
{
TimeScaledObject::ScaleChangedEvent(scale);
// Update scene rect
update_scene_rect();
// Force redraw for playhead if the above function didn't do it
viewport()->update();
}
}
-156
View File
@@ -1,156 +0,0 @@
/***
Olive - Non-Linear Video Editor
Copyright (C) 2022 Olive Team
Modifications Copyright (C) 2025 mikesolar
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
***/
#ifndef OAK_TIMELINEVIEWBASE_H
#define OAK_TIMELINEVIEWBASE_H
#include <QGraphicsView>
#include <cstdint>
#include <vector>
#include "core.h"
#include "timescaledobject.h"
#include "widget/handmovableview/handmovableview.h"
namespace olive
{
class TimeBasedWidget;
class TimeBasedView : public HandMovableView, public TimeScaledObject {
Q_OBJECT
public:
TimeBasedView(QWidget *parent = nullptr);
void enable_snap(const std::vector<Rational> &points);
void disable_snap();
bool is_snapped() const
{
return snapped_;
}
TimeBasedWidget *get_snap_service() const
{
return snap_service_;
}
void set_snap_service(TimeBasedWidget *service)
{
snap_service_ = service;
}
const double &get_y_scale() const;
void set_y_scale(const double &y_scale);
virtual bool is_dragging_playhead() const
{
return dragging_playhead_;
}
// To be called only by selection managers
virtual void SelectionManagerSelectEvent(void *obj)
{
}
virtual void SelectionManagerDeselectEvent(void *obj)
{
}
OakEngineNode *get_viewer_node() const
{
return viewer_;
}
void set_viewer_node(OakEngineNode *v);
QPointF scale_point(const QPointF &p) const;
QPointF unscale_point(const QPointF &p) const;
public slots:
void set_end_time(const Rational &length);
/**
* @brief Slot called whenever the view resizes or the scene contents change to enforce minimum scene sizes
*/
void update_scene_rect();
signals:
void scale_changed(double scale);
protected:
virtual void drawForeground(QPainter *painter, const QRectF &rect) override;
virtual void resizeEvent(QResizeEvent *event) override;
virtual void ScaleChangedEvent(const double &scale) override;
virtual void SceneRectUpdateEvent(QRectF &)
{
}
virtual void VerticalScaleChangedEvent(double scale);
virtual void zoom_into_cursor_position(QWheelEvent *event, double multiplier,
const QPointF &cursor_pos) override;
bool playhead_press(QMouseEvent *event);
bool playhead_move(QMouseEvent *event);
bool playhead_release(QMouseEvent *event);
virtual void TimebaseChangedEvent(const Rational &) override;
bool is_y_axis_enabled() const
{
return y_axis_enabled_;
}
void set_y_axis_enabled(bool e)
{
y_axis_enabled_ = e;
}
private:
qreal get_playhead_x();
double playhead_scene_left_;
double playhead_scene_right_;
bool dragging_playhead_;
QGraphicsScene scene_;
bool snapped_;
std::vector<Rational> snap_time_;
Rational end_time_;
TimeBasedWidget *snap_service_;
bool y_axis_enabled_;
double y_scale_;
OakEngineNode *viewer_;
QMetaObject::Connection viewer_conn_;
};
}
#endif // OAK_TIMELINEVIEWBASE_H
@@ -1,27 +0,0 @@
/***
Olive - Non-Linear Video Editor
Copyright (C) 2022 Olive Team
Modifications Copyright (C) 2025 mikesolar
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
***/
#include "timebasedviewselectionmanager.h"
namespace olive
{
}
@@ -1,472 +0,0 @@
/***
Olive - Non-Linear Video Editor
Copyright (C) 2022 Olive Team
Modifications Copyright (C) 2025 mikesolar
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
***/
#ifndef OAK_TIMEBASEDVIEWSELECTIONMANAGER_H
#define OAK_TIMEBASEDVIEWSELECTIONMANAGER_H
#include <QGraphicsView>
#include <QMouseEvent>
#include <QRubberBand>
#include <QToolTip>
#include "oakutil/qtutils.h"
#include "olive/core/util/timecodefunctions.h"
#include "oakengine/node.h"
#include "oakengine/undo.h"
#include "timebasedview.h"
#include "timebasedwidget.h"
#include "widget/keyframeview/keyframehandle.h"
#include "widget/timeruler/markerhandle.h"
#include "widget/timetarget/timetarget.h"
namespace olive
{
template <typename T> class TimeBasedViewSelectionManager {
public:
TimeBasedViewSelectionManager(TimeBasedView *view)
: view_(view)
, rubberband_(nullptr)
, snap_mask_(TimeBasedWidget::k_snap_all)
{
}
void set_snap_mask(TimeBasedWidget::SnapMask e)
{
snap_mask_ = e;
}
void clear_drawn_objects()
{
drawn_objects_.clear();
}
void declare_drawn_object(T *object, const QRectF &rect)
{
QRectF r(view_->unscale_point(rect.topLeft()),
view_->unscale_point(rect.bottomRight()));
drawn_objects_.push_back({ object, r });
}
bool select(T *key)
{
Q_ASSERT(key);
if (!is_selected(key)) {
selected_.push_back(key);
return true;
}
return false;
}
bool deselect(T *key)
{
Q_ASSERT(key);
auto it = std::find(selected_.cbegin(), selected_.cend(), key);
if (it == selected_.cend()) {
return false;
} else {
selected_.erase(it);
return true;
}
}
void clear_selection()
{
selected_.clear();
}
bool is_selected(T *key) const
{
return std::find(selected_.cbegin(), selected_.cend(), key) !=
selected_.cend();
}
const std::vector<T *> &get_selected_objects() const
{
return selected_;
}
void set_timebase(const Rational &tb)
{
timebase_ = tb;
}
T *get_object_at_point(const QPointF &scene_pt)
{
// Iterate in reverse order because the objects drawn later will appear on top to the user
QPointF unscaled = view_->unscale_point(scene_pt);
for (auto it = drawn_objects_.crbegin(); it != drawn_objects_.crend();
it++) {
const DrawnObject &kp = *it;
if (kp.second.contains(unscaled)) {
return kp.first;
}
}
return nullptr;
}
T *get_object_at_point(const QPoint &pt)
{
return get_object_at_point(view_->mapToScene(pt));
}
T *mouse_press(QMouseEvent *event)
{
T *key_under_cursor = nullptr;
if (event->button() == Qt::LeftButton ||
event->button() == Qt::RightButton) {
// See if there's a keyframe in this position
key_under_cursor = get_object_at_point(event->pos());
bool holding_shift = event->modifiers() & Qt::ShiftModifier;
if (!key_under_cursor || !is_selected(key_under_cursor)) {
if (!holding_shift) {
// If not already selecting and not holding shift, clear the current selection
clear_selection();
}
// Add item to selection, either nothing if shift wasn't held, or the existing selection
if (key_under_cursor) {
select(key_under_cursor);
view_->SelectionManagerSelectEvent(key_under_cursor);
}
} else if (holding_shift) {
// If selected and holding shift, de-select this item but do nothing else
deselect(key_under_cursor);
view_->SelectionManagerDeselectEvent(key_under_cursor);
key_under_cursor = nullptr;
}
}
return key_under_cursor;
}
bool is_dragging() const
{
return !dragging_.empty();
}
void drag_start(T *initial_item, QMouseEvent *event,
TimeTargetObject *target = nullptr)
{
if (event->button() != Qt::LeftButton) {
return;
}
time_target_ = target;
initial_drag_item_ = initial_item;
dragging_.resize(selected_.size());
if constexpr (std::is_same_v<T, OakEngineMarker>) {
snap_points_.resize(selected_.size() * 2);
} else {
snap_points_.resize(selected_.size());
}
if (target) {
time_targets_.resize(snap_points_.size());
memset(time_targets_.data(), 0,
time_targets_.size() * sizeof(OakEngineNode *));
} else {
time_targets_.clear();
}
for (size_t i = 0; i < selected_.size(); i++) {
T *obj = selected_.at(i);
if constexpr (std::is_same_v<T, OakEngineMarker>) {
dragging_[i] = selection_time(obj);
snap_points_[i] = selection_time(obj);
snap_points_[i + selected_.size()] = selection_time_end(obj);
if (target) {
time_targets_[i] = time_targets_[i + selected_.size()] =
selection_time_target_parent(obj);
}
} else {
dragging_[i] = selection_time(obj);
snap_points_[i] = selection_time(obj);
if (target) {
time_targets_[i] = selection_time_target_parent(obj);
}
}
}
drag_mouse_start_ =
view_->unscale_point(view_->mapToScene(event->pos()));
}
void snap_points(Rational *movement)
{
std::vector<Rational> copy = snap_points_;
if (time_target_) {
for (size_t i = 0; i < copy.size(); i++) {
if (OakEngineNode *parent = time_targets_[i]) {
copy[i] = time_target_->get_adjusted_time(
parent,
time_target_->get_time_target(), copy[i],
k_transform_towards_output);
}
}
}
if (Core::instance()->snapping() && view_->get_snap_service()) {
view_->get_snap_service()->snap_point(copy, movement, snap_mask_);
}
}
void unsnap()
{
if (view_->get_snap_service()) {
view_->get_snap_service()->hide_snaps();
}
}
void drag_move(const QPoint &local_pos,
const QString &tip_format = QString())
{
Rational time_diff =
view_->scene_to_time_no_grid(view_->mapToScene(local_pos).x() -
view_->scale_point(drag_mouse_start_).x());
// Snap points
Rational presnap_time_diff = time_diff;
snap_points(&time_diff);
// Validate snapping
if (Core::instance()->snapping() && view_->get_snap_service()) {
for (size_t i = 0; i < selected_.size(); i++) {
Rational proposed_time = dragging_.at(i) + time_diff;
T *sel = selected_.at(i);
if (selection_has_sibling_at_time(sel, proposed_time)) {
// Unsnap
time_diff = presnap_time_diff;
if (view_->get_snap_service()) {
view_->get_snap_service()->hide_snaps();
}
break;
}
}
}
// Validate movement
for (size_t i = 0; i < selected_.size(); i++) {
Rational proposed_time = dragging_.at(i) + time_diff;
T *sel = selected_.at(i);
// Magic number: use interval of 1ms to avoid collisions
Rational adj(1, 1000);
if (dragging_.at(i) < proposed_time) {
// Negate adjustment value if origin is less than proposed time
adj = -adj;
}
bool loop;
do {
loop = false;
while (selection_has_sibling_at_time(sel, proposed_time)) {
proposed_time += adj;
unsnap();
}
if (proposed_time < 0) {
// Prevent any object from going below zero
proposed_time = 0;
unsnap();
// Setting our proposed time to zero may (re)introduce a conflict that we just avoided
// with the sibling check above, so we request it to happen again. To avoid a negative
// adj bringing us back below zero, we force adj to positive so it'll only nudge higher
adj = qAbs(adj);
loop = true;
}
} while (loop);
time_diff = proposed_time - dragging_.at(i);
}
// Apply movement
for (size_t i = 0; i < selected_.size(); i++) {
selection_set_time(selected_.at(i),
dragging_.at(i) + time_diff);
}
// Show information about this keyframe
Rational display_time;
if constexpr (std::is_same_v<T, OakEngineMarker>) {
display_time = selection_time(initial_drag_item_);
} else {
display_time = selection_time(initial_drag_item_);
}
QString tip = QString::fromStdString(Timecode::time_to_timecode(
display_time, timebase_, Core::instance()->get_timecode_display(),
false));
last_used_tip_format_ = tip_format;
if (!tip_format.isEmpty()) {
tip = tip_format.arg(tip);
}
QToolTip::hideText();
QToolTip::showText(QCursor::pos(), tip);
}
void drag_stop(void *command)
{
QToolTip::hideText();
for (size_t i = 0; i < selected_.size(); i++) {
if constexpr (std::is_same_v<T, OakEngineKeyframe>) {
int tbn = 0, tbd = 0;
oakengine_node_frame_time_base(
oakengine_keyframe_get_node(selected_.at(i)),
&tbn, &tbd);
const int64_t new_ts = olive::core::Timecode::time_to_timestamp(
dragging_.at(i), olive::Rational(tbn, tbd),
olive::core::Timecode::k_round);
oakengine_undo_command_multi_add_child(
command,
oakengine_keyframe_set_time_command(
selected_.at(i),
new_ts));
} else if constexpr (std::is_same_v<T, OakEngineMarker>) {
oakengine_undo_command_multi_add_child(
command,
oakengine_marker_set_time_command(
selected_.at(i),
dragging_.at(i).numerator(),
dragging_.at(i).denominator()));
}
}
dragging_.clear();
unsnap();
}
void rubber_band_start(QMouseEvent *event)
{
if (event->button() == Qt::LeftButton ||
event->button() == Qt::RightButton) {
rubberband_scene_start_ =
view_->unscale_point(view_->mapToScene(event->pos()));
rubberband_ = new QRubberBand(QRubberBand::Rectangle, view_);
rubberband_->setGeometry(
QRect(event->pos().x(), event->pos().y(), 0, 0));
rubberband_->show();
rubberband_preselected_ = selected_;
}
}
void rubber_band_move(const QPoint &pos)
{
if (is_rubber_banding()) {
QRectF band_rect = QRectF(view_->mapFromScene(view_->scale_point(
rubberband_scene_start_)),
pos)
.normalized();
rubberband_->setGeometry(band_rect.toRect());
QPointF current = view_->unscale_point(view_->mapToScene(pos));
QRectF scene_rect =
QRectF(rubberband_scene_start_, current).normalized();
selected_ = rubberband_preselected_;
for (const DrawnObject &kp : drawn_objects_) {
if (scene_rect.intersects(kp.second)) {
select(kp.first);
}
}
}
}
void rubber_band_stop()
{
if (is_rubber_banding()) {
delete rubberband_;
rubberband_ = nullptr;
}
}
bool is_rubber_banding() const
{
return rubberband_;
}
void force_drag_update()
{
if (is_rubber_banding() || is_dragging()) {
QPoint local_pos = view_->viewport()->mapFromGlobal(QCursor::pos());
if (is_rubber_banding()) {
rubber_band_move(local_pos);
} else {
drag_move(local_pos, last_used_tip_format_);
}
}
}
private:
TimeBasedView *view_;
using DrawnObject = QPair<T *, QRectF>;
std::vector<DrawnObject> drawn_objects_;
std::vector<T *> selected_;
std::vector<Rational> dragging_;
std::vector<Rational> snap_points_;
std::vector<OakEngineNode *> time_targets_;
T *initial_drag_item_;
QPointF drag_mouse_start_;
Rational timebase_;
QRubberBand *rubberband_;
QPointF rubberband_scene_start_;
std::vector<T *> rubberband_preselected_;
TimeBasedWidget::SnapMask snap_mask_;
TimeTargetObject *time_target_;
QString last_used_tip_format_;
};
}
#endif // OAK_TIMEBASEDVIEWSELECTIONMANAGER_H
File diff suppressed because it is too large Load Diff
-331
View File
@@ -1,331 +0,0 @@
/***
Olive - Non-Linear Video Editor
Copyright (C) 2022 Olive Team
Modifications Copyright (C) 2025 mikesolar
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
***/
#ifndef OAK_TIMEBASEDWIDGET_H
#define OAK_TIMEBASEDWIDGET_H
#include <QPointer>
#include <QWidget>
// Must precede the engine viewer.h include below: engine headers inline-
// instantiate QMetaTypeId for core value types (Rational/Color), and the
// Q_DECLARE_METATYPE specializations in oakutil/qtutils.h are only legal
// if they come first.
#include "oakutil/qtutils.h"
// WAVE3: this engine include is intentionally retained. The QPointer below
// needs the complete QObject-derived ViewerOutput type, and QPointer's
// null-on-destroy semantics cannot be replicated through the facade: the C
// ABI exposes no node-destruction event (OAKENGINE_EVENT_* tops out at
// REMOVED_FROM_GRAPH, which does not fire on every teardown path, e.g.
// project destruction via the QObject parent chain) and no accessor that
// hands the node back as a QObject* for a QPointer<QObject>. The queued
// playhead-scroll slots in the .cpp dereference the pointer after engine
// teardown, so a raw OakEngineNode* would be a use-after-free there. engine/
// is frozen for R8, so the include stays until the facade grows a
// destruction notification (WRAPPER-GAP).
#include "node/output/viewer/viewer.h"
#include "timeline/timelinecommonapp.h"
#include "widget/keyframeview/keyframeviewinputconnection.h"
#include "widget/resizablescrollbar/resizabletimelinescrollbar.h"
#include "widget/timebased/timescaledobject.h"
#include "widget/timelinewidget/view/timelineview.h"
#include "widget/timetarget/timetarget.h"
namespace olive
{
class EngineEventBridge;
class TimeRuler;
class TimeBasedWidget : public TimelineScaledWidget {
Q_OBJECT
public:
TimeBasedWidget(bool ruler_text_visible = true,
bool ruler_cache_status_visible = false,
QWidget *parent = nullptr);
void zoom_in();
void zoom_out();
OakEngineNode *get_connected_node() const;
void connect_viewer_node(OakEngineNode *node);
OakEngineWorkarea *get_connected_work_area() const
{
return workarea_;
}
OakEngineMarkerList *get_connected_markers() const
{
return markers_;
}
void connect_work_area(OakEngineWorkarea *workarea);
void connect_markers(OakEngineMarkerList *markers);
void set_scale_and_center_on_playhead(const double &scale);
TimeRuler *ruler() const;
using SnapMask = uint32_t;
enum SnapPoints {
k_snap_to_clips = 0x1,
k_snap_to_playhead = 0x2,
k_snap_to_markers = 0x4,
k_snap_to_keyframes = 0x8,
k_snap_to_workarea = 0x10,
k_snap_all = UINT32_MAX
};
/**
* @brief Snaps point `start_point` that is moving by `movement` to currently existing clips
*/
bool snap_point(const std::vector<Rational> &start_times, Rational *movement,
SnapMask snap_points = k_snap_all);
void show_snaps(const std::vector<Rational> &times);
void hide_snaps();
virtual bool copy_selected(bool cut);
virtual bool paste();
public slots:
void SetTimebase(const Rational &timebase);
void SetScale(const double &scale);
void go_to_start();
void prev_frame();
void next_frame();
void go_to_end();
void go_to_prev_cut();
void go_to_next_cut();
void set_in_at_playhead();
void set_out_at_playhead();
void reset_in();
void reset_out();
void clear_in_out_points();
void set_marker();
void toggle_show_all();
void go_to_in();
void go_to_out();
void delete_selected();
protected:
ResizableTimelineScrollBar *scrollbar() const;
virtual void TimebaseChangedEvent(const Rational &) override;
virtual void TimeChangedEvent(const Rational &)
{
}
virtual void ScaleChangedEvent(const double &) override;
virtual void ConnectedNodeChangeEvent(OakEngineNode *)
{
}
virtual void ConnectedWorkAreaChangeEvent(OakEngineWorkarea *)
{
}
virtual void ConnectedMarkersChangeEvent(OakEngineMarkerList *)
{
}
EngineEventBridge *bridge_ = nullptr;
QMetaObject::Connection playhead_conn_;
virtual void ConnectNodeEvent(OakEngineNode *)
{
}
virtual void DisconnectNodeEvent(OakEngineNode *)
{
}
void set_auto_max_scroll_bar(bool e);
virtual void resizeEvent(QResizeEvent *event) override;
void connect_timeline_view(TimeBasedView *base);
void set_catch_up_scroll_value(QScrollBar *b, int v, int maximum);
void stop_catch_up_scroll_timer(QScrollBar *b);
virtual const QVector<OakEngineBlock *> *get_snap_blocks() const
{
return nullptr;
}
virtual const QVector<KeyframeViewInputConnection *> *
get_snap_keyframes() const
{
return nullptr;
}
virtual const TimeTargetObject *get_keyframe_time_target() const
{
return nullptr;
}
virtual const std::vector<OakEngineKeyframe *> *get_snap_ignore_keyframes() const
{
return nullptr;
}
virtual const std::vector<OakEngineMarker *> *get_snap_ignore_markers() const
{
return nullptr;
}
protected slots:
/**
* @brief Slot to center the horizontal scroll bar on the playhead's current position
*/
void center_scroll_on_playhead();
/**
* @brief By default, TimeBasedWidget will set the timebase to the viewer node's video timebase.
* Set this to false if you want to set your own timebase.
*/
void set_auto_set_timebase(bool e);
static void page_scroll_internal(QScrollBar *bar, int maximum,
int screen_position, bool whole_page_scroll);
void stop_catch_up_scroll_timer()
{
stop_catch_up_scroll_timer(scrollbar_);
}
void set_catch_up_scroll_value(int v);
signals:
void timebase_changed(const Rational &);
void connected_node_changed(OakEngineNode *old, OakEngineNode *now);
protected slots:
virtual void SendCatchUpScrollEvent();
private:
/**
* @brief Set either in or out point to the current playhead
*
* @param m
*
* Set to kTrimIn or kTrimOut for setting the in point or out point respectively.
*/
void set_point(TimelineApp::MovementMode m, const Rational &time);
/**
* @brief Reset either the in or out point
*
* Sets either the in point to 0 or the out point to `RATIONAL_MAX`.
*
* @param m
*
* Set to kTrimIn or kTrimOut for setting the in point or out point respectively.
*/
void reset_point(TimelineApp::MovementMode m);
void page_scroll_internal(int screen_position, bool whole_page_scroll);
bool user_is_dragging_playhead() const;
QPointer<ViewerOutput> viewer_node_;
TimeRuler *ruler_;
ResizableTimelineScrollBar *scrollbar_;
bool auto_max_scrollbar_;
QList<TimeBasedView *> timeline_views_;
bool toggle_show_all_;
double toggle_show_all_old_scale_;
int toggle_show_all_old_scroll_;
bool auto_set_timebase_;
int scrollbar_start_width_;
double scrollbar_start_value_;
double scrollbar_start_scale_;
bool scrollbar_top_handle_;
OakEngineWorkarea *workarea_;
OakEngineMarkerList *markers_;
QTimer *catchup_scroll_timer_;
struct CatchUpScrollData {
qint64 last_forced = 0;
int maximum;
int value;
};
QMap<QScrollBar *, CatchUpScrollData> catchup_scroll_values_;
private slots:
void update_maximum_scroll();
void scroll_bar_resize_began(int current_bar_width, bool top_handle);
void scroll_bar_resize_moved(int new_bar_width);
/**
* @brief Slot to handle page scrolling of the playhead
*
* If the playhead is outside the current scroll bounds, this function will scroll to where it is. Otherwise it will
* do nothing.
*/
void page_scroll_to_playhead();
void catch_up_scroll_to_playhead();
void catch_up_scroll_to_point(int point);
void catch_up_timer_timeout();
void auto_update_timebase();
void connected_node_removed_from_graph();
void playhead_time_changed(const Rational &time);
};
}
#endif // OAK_TIMEBASEDWIDGET_H
-183
View File
@@ -1,183 +0,0 @@
/***
Olive - Non-Linear Video Editor
Copyright (C) 2022 Olive Team
Modifications Copyright (C) 2025 mikesolar
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
***/
#include "timescaledobject.h"
#include <cfloat>
#include <QtMath>
#include "oakengine/node.h"
namespace olive
{
const int TimeScaledObject::k_calculate_dimensions_padding = 10;
TimeScaledObject::TimeScaledObject()
: scale_(1.0)
, min_scale_(0)
, max_scale_(oakengine_audio_waveform_max_sample_rate())
{
}
void TimeScaledObject::set_timebase(const Rational &timebase)
{
timebase_ = timebase;
timebase_dbl_ = timebase_.to_double();
TimebaseChangedEvent(timebase);
}
const Rational &TimeScaledObject::timebase() const
{
return timebase_;
}
const double &TimeScaledObject::timebase_dbl() const
{
return timebase_dbl_;
}
Rational TimeScaledObject::scene_to_time(const double &x, const double &x_scale,
const Rational &timebase, bool round)
{
if (timebase.isNull()) {
return Rational();
}
double unscaled_time = x / x_scale / timebase.to_double();
// Adjust screen point by scale and timebase
qint64 rounded_x_mvmt;
if (round) {
rounded_x_mvmt = qRound64(unscaled_time);
} else if (unscaled_time < 0) {
// "floor" to zero
rounded_x_mvmt = qCeil(unscaled_time);
} else {
rounded_x_mvmt = qFloor(unscaled_time);
}
// Return a time in the timebase
return Rational(rounded_x_mvmt * timebase.numerator(),
timebase.denominator());
}
Rational TimeScaledObject::scene_to_time_no_grid(const double &x,
const double &x_scale)
{
double unscaled_time = x / x_scale;
return Rational::from_double(unscaled_time);
}
double TimeScaledObject::time_to_scene(const Rational &time) const
{
if (timebase_.isNull()) {
return 0.0;
}
return time.to_double() * scale_;
}
Rational TimeScaledObject::scene_to_time(const double &x, bool round) const
{
if (timebase_.isNull()) {
return Rational();
}
return scene_to_time(x, scale_, timebase_, round);
}
Rational TimeScaledObject::scene_to_time_no_grid(const double &x) const
{
if (timebase_.isNull()) {
return Rational::from_double(x / scale_);
}
return scene_to_time_no_grid(x, scale_);
}
void TimeScaledObject::set_maximum_scale(const double &max)
{
max_scale_ = max;
// Keep min <= max so the clamp in set_scale() is never called with
// inverted limits (undefined behavior)
if (min_scale_ > max_scale_) {
min_scale_ = max_scale_;
}
if (get_scale() > max_scale_) {
set_scale(max_scale_);
}
}
void TimeScaledObject::set_minimum_scale(const double &min)
{
min_scale_ = min;
// Keep min <= max so the clamp in set_scale() is never called with
// inverted limits (undefined behavior)
if (max_scale_ < min_scale_) {
max_scale_ = min_scale_;
}
if (get_scale() < min_scale_) {
set_scale(min_scale_);
}
}
const double &TimeScaledObject::get_scale() const
{
return scale_;
}
void TimeScaledObject::set_scale(const double &scale)
{
Q_ASSERT(scale > 0);
scale_ = std::clamp(scale, min_scale_, max_scale_);
ScaleChangedEvent(scale_);
}
void TimeScaledObject::set_scale_from_dimensions(double viewport_width,
double content_width)
{
set_scale(calculate_scale_from_dimensions(viewport_width, content_width));
}
double TimeScaledObject::calculate_scale_from_dimensions(double viewport_sz,
double content_sz)
{
return static_cast<double>(viewport_sz / k_calculate_dimensions_padding *
(k_calculate_dimensions_padding - 1)) /
static_cast<double>(content_sz);
}
double TimeScaledObject::calculate_padding_from_dimension_scale(double viewport_sz)
{
return (viewport_sz / (k_calculate_dimensions_padding * 2));
}
TimelineScaledWidget::TimelineScaledWidget(QWidget *parent)
: QWidget(parent)
{
}
}
-105
View File
@@ -1,105 +0,0 @@
/***
Olive - Non-Linear Video Editor
Copyright (C) 2022 Olive Team
Modifications Copyright (C) 2025 mikesolar
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
***/
#ifndef OAK_TIMELINESCALEDOBJECT_H
#define OAK_TIMELINESCALEDOBJECT_H
#include <olive/core/core.h>
#include <QWidget>
namespace olive
{
// Forward declaration for consumers of this header (e.g. timebasedview.h)
// that previously got ViewerOutput transitively through engine block.h.
class ViewerOutput;
using olive::core::Rational;
/**
* @brief Provides base functionality for any object that uses time and scale
*/
class TimeScaledObject {
public:
TimeScaledObject();
virtual ~TimeScaledObject() = default;
void set_timebase(const Rational &timebase);
const Rational &timebase() const;
const double &timebase_dbl() const;
static Rational scene_to_time(const double &x, const double &x_scale,
const Rational &timebase, bool round = false);
static Rational scene_to_time_no_grid(const double &x, const double &x_scale);
const double &get_scale() const;
const double &get_maximum_scale() const
{
return max_scale_;
}
void set_scale(const double &scale);
void set_scale_from_dimensions(double viewport_width, double content_width);
static double calculate_scale_from_dimensions(double viewport_sz,
double content_sz);
static double calculate_padding_from_dimension_scale(double viewport_sz);
double time_to_scene(const Rational &time) const;
Rational scene_to_time(const double &x, bool round = false) const;
Rational scene_to_time_no_grid(const double &x) const;
protected:
virtual void TimebaseChangedEvent(const Rational &)
{
}
virtual void ScaleChangedEvent(const double &)
{
}
void set_maximum_scale(const double &max);
void set_minimum_scale(const double &min);
private:
Rational timebase_;
double timebase_dbl_;
double scale_;
double min_scale_;
double max_scale_;
static const int k_calculate_dimensions_padding;
};
class TimelineScaledWidget : public QWidget, public TimeScaledObject {
Q_OBJECT
public:
TimelineScaledWidget(QWidget *parent = nullptr);
};
}
#endif // OAK_TIMELINESCALEDOBJECT_H