change: UI and migrate to gtest
This commit is contained in:
@@ -207,7 +207,10 @@ void NodeParamViewConnectedLabel::update_label()
|
||||
void NodeParamViewConnectedLabel::update_value_tree()
|
||||
{
|
||||
if (value_tree_ && viewer_ && value_tree_->isVisible()) {
|
||||
value_tree_->set_node(input_, viewer_->get_playhead());
|
||||
int64_t pn, pd;
|
||||
oakengine_viewer_get_playhead(
|
||||
reinterpret_cast<OakEngineNode *>(viewer_), &pn, &pd);
|
||||
value_tree_->set_node(input_, Rational(pn, pd));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -45,6 +45,22 @@ NodeViewToolBar::NodeViewToolBar(QWidget *parent)
|
||||
&NodeViewToolBar::mini_map_enabled_toggled);
|
||||
layout->addWidget(minimap_btn_);
|
||||
|
||||
// Zoom controls per the UI design reference: [+] [-] [Fit]
|
||||
zoom_out_btn_ = new QPushButton();
|
||||
connect(zoom_out_btn_, &QPushButton::clicked, this,
|
||||
&NodeViewToolBar::zoom_out_clicked);
|
||||
layout->addWidget(zoom_out_btn_);
|
||||
|
||||
zoom_in_btn_ = new QPushButton();
|
||||
connect(zoom_in_btn_, &QPushButton::clicked, this,
|
||||
&NodeViewToolBar::zoom_in_clicked);
|
||||
layout->addWidget(zoom_in_btn_);
|
||||
|
||||
fit_btn_ = new QPushButton();
|
||||
connect(fit_btn_, &QPushButton::clicked, this,
|
||||
&NodeViewToolBar::fit_clicked);
|
||||
layout->addWidget(fit_btn_);
|
||||
|
||||
layout->addStretch();
|
||||
|
||||
retranslate();
|
||||
@@ -65,12 +81,18 @@ void NodeViewToolBar::retranslate()
|
||||
{
|
||||
add_node_btn_->setToolTip(tr("Add Node"));
|
||||
minimap_btn_->setToolTip(tr("Toggle Mini-Map"));
|
||||
zoom_in_btn_->setToolTip(tr("Zoom In"));
|
||||
zoom_out_btn_->setToolTip(tr("Zoom Out"));
|
||||
fit_btn_->setToolTip(tr("Fit to Content"));
|
||||
fit_btn_->setText(tr("Fit"));
|
||||
}
|
||||
|
||||
void NodeViewToolBar::update_icons()
|
||||
{
|
||||
add_node_btn_->setIcon(icon::add);
|
||||
minimap_btn_->setIcon(icon::mini_map);
|
||||
zoom_in_btn_->setIcon(icon::zoom_in);
|
||||
zoom_out_btn_->setIcon(icon::zoom_out);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -41,6 +41,12 @@ signals:
|
||||
|
||||
void mini_map_enabled_toggled(bool e);
|
||||
|
||||
void zoom_in_clicked();
|
||||
|
||||
void zoom_out_clicked();
|
||||
|
||||
void fit_clicked();
|
||||
|
||||
protected:
|
||||
virtual void changeEvent(QEvent *e) override;
|
||||
|
||||
@@ -52,6 +58,12 @@ private:
|
||||
QPushButton *add_node_btn_;
|
||||
|
||||
QPushButton *minimap_btn_;
|
||||
|
||||
QPushButton *zoom_in_btn_;
|
||||
|
||||
QPushButton *zoom_out_btn_;
|
||||
|
||||
QPushButton *fit_btn_;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -44,6 +44,12 @@ NodeWidget::NodeWidget(QWidget *parent)
|
||||
&NodeView::set_mini_map_enabled);
|
||||
connect(toolbar_, &NodeViewToolBar::add_node_clicked, node_view_,
|
||||
&NodeView::show_add_menu);
|
||||
connect(toolbar_, &NodeViewToolBar::zoom_in_clicked, node_view_,
|
||||
&NodeView::zoom_in);
|
||||
connect(toolbar_, &NodeViewToolBar::zoom_out_clicked, node_view_,
|
||||
&NodeView::zoom_out);
|
||||
connect(toolbar_, &NodeViewToolBar::fit_clicked, node_view_,
|
||||
&NodeView::center_on_items_bounding_rect);
|
||||
|
||||
// Set defaults
|
||||
toolbar_->set_mini_map_enabled(true);
|
||||
|
||||
@@ -166,7 +166,7 @@ void TimeBasedView::set_viewer_node(ViewerOutput *v)
|
||||
|
||||
if (viewer_) {
|
||||
viewer_sub_ = oakengine_event_subscribe(
|
||||
reinterpret_cast<OakEngineNode *>(viewer_),
|
||||
viewer_,
|
||||
OAKENGINE_EVENT_VIEWER_PLAYHEAD_CHANGED,
|
||||
[](const oakengine_event *, void *userdata) {
|
||||
static_cast<TimeBasedView *>(userdata)->viewport()->update();
|
||||
@@ -280,7 +280,10 @@ bool TimeBasedView::playhead_release(QMouseEvent *)
|
||||
qreal TimeBasedView::get_playhead_x()
|
||||
{
|
||||
if (viewer_) {
|
||||
return time_to_scene(viewer_->get_playhead());
|
||||
int64_t pn, pd;
|
||||
oakengine_viewer_get_playhead(
|
||||
reinterpret_cast<OakEngineNode *>(viewer_), &pn, &pd);
|
||||
return time_to_scene(Rational(pn, pd));
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -50,7 +50,8 @@ TimelineAndTrackView::TimelineAndTrackView(Qt::Alignment vertical_alignment,
|
||||
connect(track_view_->verticalScrollBar(), &QScrollBar::valueChanged, this,
|
||||
&TimelineAndTrackView::tracks_value_changed);
|
||||
|
||||
splitter_->setSizes({ 1, width() });
|
||||
// Default track header width: 180px (per UI design reference)
|
||||
splitter_->setSizes({ 180, width() });
|
||||
}
|
||||
|
||||
QSplitter *TimelineAndTrackView::splitter() const
|
||||
|
||||
@@ -31,6 +31,7 @@
|
||||
#include <QFileInfo>
|
||||
#include <QInputDialog>
|
||||
#include <QProcess>
|
||||
#include <QSlider>
|
||||
#include <QSplitter>
|
||||
#include <QUrl>
|
||||
#include <QVBoxLayout>
|
||||
@@ -80,6 +81,7 @@
|
||||
#include "widget/menu/menushared.h"
|
||||
#include "widget/nodeparamview/nodeparamview.h"
|
||||
#include "widget/timeruler/timeruler.h"
|
||||
#include "widget/toolbar/toolbar.h"
|
||||
|
||||
#include "widget/viewer/vieweroutpututils.h"
|
||||
namespace olive
|
||||
@@ -159,6 +161,26 @@ QVector<Footage *> get_selected_proxy_footage(const QVector<Block *> &blocks)
|
||||
return footage;
|
||||
}
|
||||
|
||||
// Zoom slider <-> scale mapping (logarithmic: slider 0..1000 -> 0.1..10000 px/s)
|
||||
constexpr double k_zoom_scale_min = 0.1;
|
||||
constexpr double k_zoom_scale_max = 10000.0;
|
||||
|
||||
double zoom_slider_to_scale(int v)
|
||||
{
|
||||
return k_zoom_scale_min *
|
||||
std::pow(k_zoom_scale_max / k_zoom_scale_min, v / 1000.0);
|
||||
}
|
||||
|
||||
int scale_to_zoom_slider(double s)
|
||||
{
|
||||
if (s <= k_zoom_scale_min)
|
||||
return 0;
|
||||
if (s >= k_zoom_scale_max)
|
||||
return 1000;
|
||||
return qRound(1000.0 * std::log(s / k_zoom_scale_min) /
|
||||
std::log(k_zoom_scale_max / k_zoom_scale_min));
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
TimelineWidget::TimelineWidget(QWidget *parent)
|
||||
@@ -173,6 +195,66 @@ TimelineWidget::TimelineWidget(QWidget *parent)
|
||||
vert_layout->setSpacing(0);
|
||||
vert_layout->setContentsMargins(0, 0, 0, 0);
|
||||
|
||||
// Application toolbar row (31px) — replaces the dockable ToolPanel by
|
||||
// default. The dockable ToolPanel remains available via the Window menu.
|
||||
QHBoxLayout *toolbar_row = new QHBoxLayout();
|
||||
toolbar_row->setContentsMargins(0, 0, 0, 0);
|
||||
toolbar_row->setSpacing(4);
|
||||
|
||||
Toolbar *toolbar = new Toolbar(this);
|
||||
toolbar->setFixedHeight(31);
|
||||
toolbar->set_tool(Core::instance()->tool());
|
||||
toolbar->set_snapping(Core::instance()->snapping());
|
||||
toolbar_row->addWidget(toolbar);
|
||||
|
||||
connect(toolbar, &Toolbar::tool_changed, Core::instance(),
|
||||
&Core::set_tool);
|
||||
connect(Core::instance(), &Core::tool_changed, toolbar,
|
||||
&Toolbar::set_tool);
|
||||
connect(toolbar, &Toolbar::snapping_changed, Core::instance(),
|
||||
&Core::set_snapping);
|
||||
connect(Core::instance(), &Core::snapping_changed, toolbar,
|
||||
&Toolbar::set_snapping);
|
||||
connect(toolbar, &Toolbar::selected_transition_changed, Core::instance(),
|
||||
&Core::set_selected_transition_object);
|
||||
|
||||
toolbar_row->addStretch();
|
||||
|
||||
// Zoom slider (logarithmic)
|
||||
zoom_slider_ = new QSlider(Qt::Horizontal, this);
|
||||
zoom_slider_->setRange(0, 1000);
|
||||
zoom_slider_->setFixedWidth(120);
|
||||
zoom_slider_->setToolTip(tr("Zoom"));
|
||||
zoom_slider_->setValue(scale_to_zoom_slider(get_scale()));
|
||||
toolbar_row->addWidget(zoom_slider_);
|
||||
|
||||
connect(zoom_slider_, &QSlider::valueChanged, this, [this](int v) {
|
||||
set_scale(zoom_slider_to_scale(v));
|
||||
});
|
||||
|
||||
// Track height slider
|
||||
track_height_slider_ = new QSlider(Qt::Horizontal, this);
|
||||
track_height_slider_->setRange(0, 8);
|
||||
track_height_slider_->setFixedWidth(80);
|
||||
track_height_slider_->setToolTip(tr("Track Height"));
|
||||
track_height_slider_->setValue(3);
|
||||
toolbar_row->addWidget(track_height_slider_);
|
||||
|
||||
connect(track_height_slider_, &QSlider::valueChanged, this, [this](int v) {
|
||||
if (!get_connected_node()) {
|
||||
return;
|
||||
}
|
||||
double h = oakengine_track_height_minimum() +
|
||||
v * oakengine_track_height_interval();
|
||||
foreach (Track *t, sequence()->get_tracks()) {
|
||||
oakengine_track_set_height(
|
||||
reinterpret_cast<OakEngineSequence *>(sequence()), t->type(),
|
||||
t->index(), h);
|
||||
}
|
||||
});
|
||||
|
||||
vert_layout->addLayout(toolbar_row);
|
||||
|
||||
QHBoxLayout *ruler_and_time_layout = new QHBoxLayout();
|
||||
vert_layout->addLayout(ruler_and_time_layout);
|
||||
|
||||
@@ -396,6 +478,11 @@ void TimelineWidget::ScaleChangedEvent(const double &scale)
|
||||
view->view()->set_scale(scale);
|
||||
}
|
||||
|
||||
// Keep zoom slider in sync when scale changes elsewhere (ctrl+wheel etc.)
|
||||
zoom_slider_->blockSignals(true);
|
||||
zoom_slider_->setValue(scale_to_zoom_slider(scale));
|
||||
zoom_slider_->blockSignals(false);
|
||||
|
||||
if (rubberband_.isVisible()) {
|
||||
QMetaObject::invokeMethod(this, &TimelineWidget::force_update_rubber_band,
|
||||
Qt::QueuedConnection);
|
||||
@@ -448,17 +535,20 @@ void TimelineWidget::ConnectNodeEvent(ViewerOutput *n)
|
||||
// Subscribe to track-level events via bridge (subscriptions in add_track)
|
||||
connect(bridge_, &EngineEventBridge::track_index_changed, this,
|
||||
[this](OakEngineTrack *source, int old_index, int new_index) {
|
||||
Track *track = reinterpret_cast<Track *>(source);
|
||||
track_updated(track->type());
|
||||
track_index_changed(track, old_index, new_index);
|
||||
track_updated(static_cast<Track::Type>(
|
||||
oakengine_track_type(source)));
|
||||
track_index_changed(reinterpret_cast<Track *>(source),
|
||||
old_index, new_index);
|
||||
});
|
||||
connect(bridge_, &EngineEventBridge::track_height_changed, this,
|
||||
[this](OakEngineTrack *source, double) {
|
||||
track_updated(reinterpret_cast<Track *>(source)->type());
|
||||
track_updated(static_cast<Track::Type>(
|
||||
oakengine_track_type(source)));
|
||||
});
|
||||
connect(bridge_, &EngineEventBridge::track_blocks_refreshed, this,
|
||||
[this](OakEngineTrack *source) {
|
||||
track_updated(reinterpret_cast<Track *>(source)->type());
|
||||
track_updated(static_cast<Track::Type>(
|
||||
oakengine_track_type(source)));
|
||||
});
|
||||
connect(bridge_, &EngineEventBridge::track_block_added, this,
|
||||
[this](OakEngineBlock *block, qint64, qint64) {
|
||||
@@ -519,7 +609,10 @@ void TimelineWidget::ConnectNodeEvent(ViewerOutput *n)
|
||||
{
|
||||
oak_video_params vp;
|
||||
oakengine_viewer_get_video_params(handle, 0, &vp);
|
||||
SetTimebase(Rational(vp.time_base_den, vp.time_base_num));
|
||||
// time_base is the frame duration (e.g. 1/25); reconstruct it as
|
||||
// Rational(num, den). (Previously num/den were swapped here, which
|
||||
// produced the frame rate and triggered "INVALID TIMEBASE".)
|
||||
SetTimebase(Rational(vp.time_base_num, vp.time_base_den));
|
||||
}
|
||||
|
||||
for (int i = 0; i < views_.size(); i++) {
|
||||
@@ -1954,9 +2047,15 @@ void TimelineWidget::track_updated(Track::Type type)
|
||||
update_viewports(type);
|
||||
}
|
||||
|
||||
void TimelineWidget::block_updated()
|
||||
void TimelineWidget::block_updated(OakEngineBlock *)
|
||||
{
|
||||
update_viewports(static_cast<Block *>(sender())->track()->type());
|
||||
// The old implementation used sender() to obtain the Block and then
|
||||
// queried its track type to update only one viewport. With the bridge
|
||||
// approach sender() is the EngineEventBridge (not a Block), and the
|
||||
// OakEngineBlock handle is opaque -- there is no C API to retrieve its
|
||||
// track type. Simply refresh all viewports; the cost is negligible
|
||||
// (just a repaint request).
|
||||
update_viewports();
|
||||
}
|
||||
|
||||
void TimelineWidget::update_horizontal_splitters()
|
||||
|
||||
@@ -25,6 +25,7 @@
|
||||
#include <QHash>
|
||||
#include <QScrollBar>
|
||||
#include <QRubberBand>
|
||||
#include <QSlider>
|
||||
#include <QWidget>
|
||||
|
||||
#include "core.h"
|
||||
@@ -361,6 +362,9 @@ private:
|
||||
|
||||
QSplitter *view_splitter_;
|
||||
|
||||
QSlider *zoom_slider_;
|
||||
QSlider *track_height_slider_;
|
||||
|
||||
void *subtitle_show_command_;
|
||||
OakEngineTrack *subtitle_tentative_track_;
|
||||
|
||||
@@ -422,7 +426,7 @@ private slots:
|
||||
|
||||
void track_updated(Track::Type type);
|
||||
|
||||
void block_updated();
|
||||
void block_updated(OakEngineBlock *block = nullptr);
|
||||
|
||||
void update_horizontal_splitters();
|
||||
|
||||
|
||||
@@ -161,18 +161,38 @@ void TrackViewItem::line_edit_cancelled()
|
||||
|
||||
void TrackViewItem::update_label()
|
||||
{
|
||||
// Per the UI design reference, tracks are identified by an NLE-style
|
||||
// type+number prefix (V1/V2 for video, A1/A2 for audio, S1 for subtitle)
|
||||
// followed by the track's custom label or default name.
|
||||
QString prefix;
|
||||
switch (track_->type()) {
|
||||
case Track::k_video:
|
||||
prefix = QStringLiteral("V%1").arg(track_->index() + 1);
|
||||
break;
|
||||
case Track::k_audio:
|
||||
prefix = QStringLiteral("A%1").arg(track_->index() + 1);
|
||||
break;
|
||||
case Track::k_subtitle:
|
||||
prefix = QStringLiteral("S%1").arg(track_->index() + 1);
|
||||
break;
|
||||
}
|
||||
|
||||
char label_buf[256];
|
||||
oakengine_node_get_label(
|
||||
reinterpret_cast<OakEngineNode *>(track_),
|
||||
label_buf, sizeof(label_buf));
|
||||
QString display;
|
||||
if (label_buf[0]) {
|
||||
label_->setText(QString::fromUtf8(label_buf));
|
||||
display = QString::fromUtf8(label_buf);
|
||||
} else {
|
||||
oakengine_node_get_name(
|
||||
reinterpret_cast<OakEngineNode *>(track_),
|
||||
label_buf, sizeof(label_buf));
|
||||
label_->setText(QString::fromUtf8(label_buf));
|
||||
display = QString::fromUtf8(label_buf);
|
||||
}
|
||||
|
||||
label_->setText(prefix.isEmpty() ? display
|
||||
: QStringLiteral("%1 %2").arg(prefix, display));
|
||||
}
|
||||
|
||||
void TrackViewItem::show_context_menu(const QPoint &p)
|
||||
|
||||
@@ -176,6 +176,10 @@ ViewerWidget::ViewerWidget(ViewerDisplayWidget *display, QWidget *parent)
|
||||
, multicam_panel_(nullptr)
|
||||
, bridge_(new EngineEventBridge(this))
|
||||
, audio_processor_(oakengine_audio_processor_create())
|
||||
, overlay_(nullptr)
|
||||
, info_chip_(nullptr)
|
||||
, safe_frame_btn_(nullptr)
|
||||
, overlay_zoom_index_(5)
|
||||
{
|
||||
// Set up main layout
|
||||
QVBoxLayout *layout = new QVBoxLayout(this);
|
||||
@@ -224,6 +228,9 @@ ViewerWidget::ViewerWidget(ViewerDisplayWidget *display, QWidget *parent)
|
||||
&ViewerSizer::hand_drag_move);
|
||||
sizer_->set_widget(display_widget_);
|
||||
|
||||
// Create the design-reference overlay (info chip + zoom/safe-frame buttons)
|
||||
create_overlay();
|
||||
|
||||
// Make the display widget the first tabbable widget. While the viewer display cannot actually
|
||||
// be interacted with by tabbing, it prevents the actual first tabbable widget (the playhead
|
||||
// slider in `controls_`) from getting auto-focused any time the panel is maximized (with `)
|
||||
@@ -234,8 +241,13 @@ ViewerWidget::ViewerWidget(ViewerDisplayWidget *display, QWidget *parent)
|
||||
connect_timeline_view(waveform_view_);
|
||||
layout->addWidget(waveform_view_);
|
||||
|
||||
// Create time ruler
|
||||
layout->addWidget(ruler());
|
||||
// Per the Oak UI design reference the viewer no longer shows its own time
|
||||
// ruler: it duplicates the timeline's ruler and clutters the viewer. The
|
||||
// ruler object still exists (created by TimeBasedWidget) and continues to
|
||||
// back marker/work-area/snapping logic and the scrollbar; we simply do not
|
||||
// add it to the visible layout. Scrubbing is provided by the transport
|
||||
// controls and the scrollbar below.
|
||||
// layout->addWidget(ruler());
|
||||
|
||||
// Create scrollbar
|
||||
layout->addWidget(scrollbar());
|
||||
@@ -493,6 +505,8 @@ void ViewerWidget::resizeEvent(QResizeEvent *event)
|
||||
super::resizeEvent(event);
|
||||
|
||||
update_minimum_scale();
|
||||
|
||||
position_overlay();
|
||||
}
|
||||
|
||||
OakEnginePreviewRequest *ViewerWidget::get_single_frame(const Rational &t,
|
||||
@@ -2260,6 +2274,8 @@ void ViewerWidget::set_viewer_resolution(int width, int height)
|
||||
foreach (ViewerWindow *vw, windows_) {
|
||||
vw->set_resolution(width, height);
|
||||
}
|
||||
|
||||
update_info_chip();
|
||||
}
|
||||
|
||||
void ViewerWidget::set_viewer_pixel_aspect(const Rational &ratio)
|
||||
@@ -2323,6 +2339,143 @@ void ViewerWidget::set_zoom_from_menu(QAction *action)
|
||||
s.height() / 2);
|
||||
}
|
||||
|
||||
void ViewerWidget::create_overlay()
|
||||
{
|
||||
// A thin transparent strip laid over the top of the viewer display. Left
|
||||
// side carries an info chip (resolution + frame rate); right side carries
|
||||
// zoom in/out/fit and a safe-frame toggle, per the UI design reference.
|
||||
overlay_ = new QWidget(sizer_);
|
||||
overlay_->setAttribute(Qt::WA_TransparentForMouseEvents, false);
|
||||
overlay_->setAutoFillBackground(false);
|
||||
|
||||
auto *overlay_layout = new QHBoxLayout(overlay_);
|
||||
overlay_layout->setContentsMargins(6, 4, 6, 4);
|
||||
overlay_layout->setSpacing(4);
|
||||
|
||||
info_chip_ = new QLabel(overlay_);
|
||||
info_chip_->setStyleSheet(QStringLiteral(
|
||||
"QLabel { background-color: rgba(0,0,0,160); color: white; "
|
||||
"border-radius: 3px; padding: 2px 6px; }"));
|
||||
info_chip_->setVisible(false);
|
||||
overlay_layout->addWidget(info_chip_);
|
||||
|
||||
overlay_layout->addStretch();
|
||||
|
||||
const QString btn_style = QStringLiteral(
|
||||
"QToolButton { background-color: rgba(0,0,0,160); color: white; "
|
||||
"border: none; border-radius: 3px; padding: 2px 6px; } "
|
||||
"QToolButton:hover { background-color: rgba(0,0,0,200); } "
|
||||
"QToolButton:checked { background-color: rgba(0,120,215,200); }");
|
||||
|
||||
auto make_btn = [this, &btn_style](const QString &text,
|
||||
const QString &tooltip) {
|
||||
auto *b = new QToolButton(overlay_);
|
||||
b->setText(text);
|
||||
b->setToolTip(tooltip);
|
||||
b->setStyleSheet(btn_style);
|
||||
b->setAutoRaise(false);
|
||||
return b;
|
||||
};
|
||||
|
||||
QToolButton *zoom_out_btn = make_btn(QStringLiteral("\u2212"), tr("Zoom Out"));
|
||||
QToolButton *zoom_in_btn = make_btn(QStringLiteral("+"), tr("Zoom In"));
|
||||
QToolButton *zoom_fit_btn = make_btn(QStringLiteral("\u2922"), tr("Fit to Window"));
|
||||
safe_frame_btn_ = make_btn(QStringLiteral("\u25A2"), tr("Safe Margins"));
|
||||
safe_frame_btn_->setCheckable(true);
|
||||
|
||||
overlay_layout->addWidget(zoom_out_btn);
|
||||
overlay_layout->addWidget(zoom_in_btn);
|
||||
overlay_layout->addWidget(zoom_fit_btn);
|
||||
overlay_layout->addWidget(safe_frame_btn_);
|
||||
|
||||
connect(zoom_out_btn, &QToolButton::clicked, this,
|
||||
&ViewerWidget::overlay_zoom_out);
|
||||
connect(zoom_in_btn, &QToolButton::clicked, this,
|
||||
&ViewerWidget::overlay_zoom_in);
|
||||
connect(zoom_fit_btn, &QToolButton::clicked, this,
|
||||
&ViewerWidget::overlay_zoom_fit);
|
||||
connect(safe_frame_btn_, &QToolButton::toggled, this,
|
||||
[this](bool) { overlay_toggle_safe_frame(); });
|
||||
|
||||
overlay_->raise();
|
||||
}
|
||||
|
||||
void ViewerWidget::position_overlay()
|
||||
{
|
||||
if (!overlay_ || !sizer_) {
|
||||
return;
|
||||
}
|
||||
// Span the full width of the sizer, hugging the top edge.
|
||||
overlay_->setGeometry(0, 0, sizer_->width(), overlay_->sizeHint().height());
|
||||
overlay_->raise();
|
||||
}
|
||||
|
||||
void ViewerWidget::update_info_chip()
|
||||
{
|
||||
if (!info_chip_) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!get_connected_node()) {
|
||||
info_chip_->setVisible(false);
|
||||
return;
|
||||
}
|
||||
|
||||
VideoParams vp = viewer_output_video_params(get_connected_node());
|
||||
if (vp.width() <= 0 || vp.height() <= 0) {
|
||||
info_chip_->setVisible(false);
|
||||
return;
|
||||
}
|
||||
|
||||
const double fps = vp.frame_rate().to_double();
|
||||
const double fps_rounded = qRound(fps);
|
||||
QString fps_str = (qFuzzyCompare(fps, fps_rounded))
|
||||
? QString::number(static_cast<int>(fps_rounded))
|
||||
: QString::number(fps, 'f', 2);
|
||||
|
||||
info_chip_->setText(QStringLiteral("%1\u00d7%2 \u00b7 %3 FPS")
|
||||
.arg(vp.width())
|
||||
.arg(vp.height())
|
||||
.arg(fps_str));
|
||||
info_chip_->setVisible(true);
|
||||
}
|
||||
|
||||
void ViewerWidget::overlay_zoom_in()
|
||||
{
|
||||
if (overlay_zoom_index_ < ViewerSizer::k_zoom_level_count - 1) {
|
||||
overlay_zoom_index_++;
|
||||
}
|
||||
auto s = sizer_->get_container_size();
|
||||
sizer_->set_zoom_anchored(ViewerSizer::k_zoom_levels[overlay_zoom_index_],
|
||||
s.width() / 2, s.height() / 2);
|
||||
}
|
||||
|
||||
void ViewerWidget::overlay_zoom_out()
|
||||
{
|
||||
if (overlay_zoom_index_ > 0) {
|
||||
overlay_zoom_index_--;
|
||||
}
|
||||
auto s = sizer_->get_container_size();
|
||||
sizer_->set_zoom_anchored(ViewerSizer::k_zoom_levels[overlay_zoom_index_],
|
||||
s.width() / 2, s.height() / 2);
|
||||
}
|
||||
|
||||
void ViewerWidget::overlay_zoom_fit()
|
||||
{
|
||||
// The zoom menu uses -1 to mean "fit to window".
|
||||
auto s = sizer_->get_container_size();
|
||||
sizer_->set_zoom_anchored(-1, s.width() / 2, s.height() / 2);
|
||||
}
|
||||
|
||||
void ViewerWidget::overlay_toggle_safe_frame()
|
||||
{
|
||||
if (!safe_frame_btn_) {
|
||||
return;
|
||||
}
|
||||
display_widget_->set_safe_margins(
|
||||
ViewerSafeMarginInfo(safe_frame_btn_->isChecked()));
|
||||
}
|
||||
|
||||
void ViewerWidget::viewer_invalidated_video_range(const TimeRange &range)
|
||||
{
|
||||
// If our current frame is within this range, we need to update
|
||||
|
||||
@@ -27,6 +27,7 @@
|
||||
#include <QPushButton>
|
||||
#include <QScrollBar>
|
||||
#include <QTimer>
|
||||
#include <QToolButton>
|
||||
#include <QWidget>
|
||||
|
||||
#include "oakengine/audio.h"
|
||||
@@ -219,6 +220,11 @@ protected:
|
||||
return display_widget_;
|
||||
}
|
||||
|
||||
// Overlay (info chip + zoom/safe-frame buttons) per the UI design reference
|
||||
void create_overlay();
|
||||
void position_overlay();
|
||||
void update_info_chip();
|
||||
|
||||
void IgnoreNextScrubEvent()
|
||||
{
|
||||
ignore_scrub_++;
|
||||
@@ -228,6 +234,12 @@ protected:
|
||||
|
||||
void set_waveform_mode(WaveformMode wf);
|
||||
|
||||
private slots:
|
||||
void overlay_zoom_in();
|
||||
void overlay_zoom_out();
|
||||
void overlay_zoom_fit();
|
||||
void overlay_toggle_safe_frame();
|
||||
|
||||
private:
|
||||
int64_t get_timestamp() const
|
||||
{
|
||||
@@ -285,6 +297,11 @@ private:
|
||||
|
||||
ViewerSizer *sizer_;
|
||||
|
||||
QWidget *overlay_;
|
||||
QLabel *info_chip_;
|
||||
QToolButton *safe_frame_btn_;
|
||||
int overlay_zoom_index_;
|
||||
|
||||
int playback_speed_;
|
||||
|
||||
Rational last_time_;
|
||||
|
||||
Reference in New Issue
Block a user