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:
@@ -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
|
||||
)
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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
@@ -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> ×);
|
||||
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
|
||||
@@ -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)
|
||||
{
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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
|
||||
Reference in New Issue
Block a user