viewer: extended scrolling/panning functionality

This commit is contained in:
itsmattkc
2020-09-06 17:40:53 +10:00
parent ba64313dd7
commit 83c7596038
5 changed files with 286 additions and 178 deletions
+3 -2
View File
@@ -75,8 +75,9 @@ ViewerWidget::ViewerWidget(QWidget *parent) :
connect(display_widget_, &ViewerDisplayWidget::CursorColor, this, &ViewerWidget::CursorColor);
connect(display_widget_, &ViewerDisplayWidget::ColorProcessorChanged, this, &ViewerWidget::ColorProcessorChanged);
connect(display_widget_, &ViewerDisplayWidget::ColorManagerChanged, this, &ViewerWidget::ColorManagerChanged);
connect(sizer_, &ViewerSizer::RequestMatrix, display_widget_, &ViewerDisplayWidget::SetMatrixZoom);
connect(sizer_, &ViewerSizer::IsZoomed, display_widget_, &ViewerDisplayWidget::IsZoomed);
connect(sizer_, &ViewerSizer::RequestScale, display_widget_, &ViewerDisplayWidget::SetMatrixZoom);
connect(sizer_, &ViewerSizer::RequestTranslate, display_widget_, &ViewerDisplayWidget::SetMatrixTranslate);
connect(display_widget_, &ViewerDisplayWidget::HandDragMoved, sizer_, &ViewerSizer::HandDragMove);
sizer_->SetWidget(display_widget_);
// Create waveform view when audio is connected and video isn't
+110 -110
View File
@@ -45,13 +45,13 @@ ViewerDisplayWidget::ViewerDisplayWidget(QWidget *parent) :
gizmos_(nullptr),
gizmo_click_(false),
last_loaded_buffer_(nullptr),
zoomed_(false),
hand_tool_clicked_(false),
hand_dragging_(false),
deinterlace_(false)
{
connect(Core::instance(), &Core::ToolChanged, this, &ViewerDisplayWidget::ToolChanged);
// Initilises hand_tool_ based on currently selected tool.
ToolChanged(Core::instance()->tool());
connect(Core::instance(), &Core::ToolChanged, this, &ViewerDisplayWidget::UpdateCursor);
// Initializes cursor based on tool
UpdateCursor();
}
ViewerDisplayWidget::~ViewerDisplayWidget()
@@ -62,49 +62,32 @@ ViewerDisplayWidget::~ViewerDisplayWidget()
void ViewerDisplayWidget::SetMatrixTranslate(const QMatrix4x4 &mat)
{
translate_matrix_ = mat;
update();
UpdateMatrix();
}
void ViewerDisplayWidget::SetMatrixZoom(const QMatrix4x4 &mat)
{
scale_matrix_ = mat;
update();
UpdateMatrix();
}
void ViewerDisplayWidget::IsZoomed(bool flag)
void ViewerDisplayWidget::UpdateCursor()
{
zoomed_ = flag;
// If the image is smaller than the container widget we reset the translation.
if (!flag) {
QMatrix4x4 mat; // defaults to identity matrix.
SetMatrixTranslate(mat);
}
}
void ViewerDisplayWidget::ToolChanged(Tool::Item tool)
{
if (tool == Tool::kHand) {
hand_tool_ = true;
if (Core::instance()->tool() == Tool::kHand) {
setCursor(Qt::OpenHandCursor);
} else {
hand_tool_ = false;
unsetCursor();
}
}
QMatrix4x4 ViewerDisplayWidget::GetCompleteMatrix()
QMatrix4x4 ViewerDisplayWidget::GetCompleteMatrixFlippedYTranslation()
{
QMatrix4x4 mat;
// Images is scaled, then translated.
return translate_matrix_ * scale_matrix_ * mat;
}
QMatrix4x4 mat = combined_matrix_;
mat.data()[13] *= -1.0f;
QMatrix4x4 ViewerDisplayWidget::GetCompleteMatrixFlippedYTranslation() {
QMatrix4x4 mat;
// Image is scaled, then translated.
mat = translate_matrix_ * scale_matrix_ * mat;
// Y translation is flipped for OpenGL usage
*(mat.data() + 13) = *(mat.data() + 13) * -1.0f;
return mat;
}
@@ -188,28 +171,6 @@ FramePtr ViewerDisplayWidget::last_loaded_buffer() const
return last_loaded_buffer_;
}
QTransform ViewerDisplayWidget::GenerateWorldTransform()
{
/*
* Get matrix elements (roughly) as below in column major order
*
* | Sx 0 0 Tx |
* | 0 Sy 0 Ty |
* | 0 0 Sz Tz |
* | 0 0 0 1 |
*/
float *data = GetCompleteMatrix().data();
QTransform world;
// Move corner of canvas to correct point
world.translate(width() * 0.5 - width() * *(data)*0.5, height() * 0.5 - height() * *(data + 5) * 0.5);
// Scale
world.scale(*(data), *(data + 5));
// Translate for mouse movement
world.translate(*(data + 12) * width() * 0.5 / *(data), *(data + 13) * height() * 0.5 / *(data + 5));
return world;
}
QPoint ViewerDisplayWidget::TransformViewerSpaceToBufferSpace(QPoint pos)
{
/*
@@ -223,62 +184,35 @@ void ViewerDisplayWidget::mousePressEvent(QMouseEvent *event)
{
if (event->button() == Qt::LeftButton && gizmos_
&& gizmos_->GizmoPress(gizmo_db_, TransformViewerSpaceToBufferSpace(event->pos()),
QVector2D(GetTexturePosition(size())), size())) {
QVector2D(GetTexturePosition(size())), size())) {
// Handle gizmo click
gizmo_click_ = true;
gizmo_drag_time_ = GetGizmoTime();
return;
}
// Reset translation.
if (event->button() == Qt::MiddleButton && event->modifiers() & Qt::ControlModifier) {
QMatrix4x4 mat; // Identity matrix
SetMatrixTranslate(mat);
return;
}
} else if (IsHandDrag(event)) {
// If translation is enabled get current position in preperation for move event.
if ((event->button() == Qt::MiddleButton || hand_tool_) && zoomed_) {
position_ = event->pos();
if (hand_tool_) hand_tool_clicked_ = true;
return;
}
// Handle hand drag
hand_last_drag_pos_ = event->pos();
hand_dragging_ = true;
emit HandDragStarted();
setCursor(Qt::ClosedHandCursor);
QOpenGLWidget::mousePressEvent(event);
} else {
if (event->button() == Qt::LeftButton) {
// Handle standard drag
emit DragStarted();
}
QOpenGLWidget::mousePressEvent(event);
if (event->button() == Qt::LeftButton) {
emit DragStarted();
}
}
void ViewerDisplayWidget::mouseMoveEvent(QMouseEvent *event)
{
// Only allow translation if the image is larger than the container widget
if ((event->buttons() & Qt::MiddleButton || hand_tool_clicked_) && zoomed_) {
QPointF delta = event->pos() - position_;
// Scale delta to widget size
delta.setX(2 * delta.x() / width());
delta.setY(2 * delta.y() / height());
QMatrix4x4 mat;
mat = GetMatrixTranslate();
mat.translate(delta.x(), delta.y());
SetMatrixTranslate(mat);
// Get new start position
position_ = event-> pos();
return;
}
if (gizmo_click_) {
gizmos_->GizmoMove(TransformViewerSpaceToBufferSpace(event->pos()),
QVector2D(GetTexturePosition(size())), gizmo_drag_time_);
update();
return;
}
QOpenGLWidget::mouseMoveEvent(event);
// Do this no matter what, emits signal to any pixel samplers
if (signal_cursor_color_) {
Color reference, display;
@@ -287,7 +221,7 @@ void ViewerDisplayWidget::mouseMoveEvent(QMouseEvent *event)
static_cast<float>(event->y()) / static_cast<float>(height()) * 2.0f - 1.0f,
0);
pixel_pos = GetCompleteMatrix().inverted() * pixel_pos;
pixel_pos = GenerateWorldTransform().inverted() * pixel_pos;
int frame_x = qRound((pixel_pos.x() + 1.0f) * 0.5f * last_loaded_buffer_->width());
int frame_y = qRound((pixel_pos.y() + 1.0f) * 0.5f * last_loaded_buffer_->height());
@@ -298,20 +232,52 @@ void ViewerDisplayWidget::mouseMoveEvent(QMouseEvent *event)
emit CursorColor(reference, display);
}
// Handle hand dragging
if (hand_dragging_) {
// Emit movement
emit HandDragMoved(event->x() - hand_last_drag_pos_.x(),
event->y() - hand_last_drag_pos_.y());
hand_last_drag_pos_ = event->pos();
} else if (gizmo_click_) {
// Signal movement
gizmos_->GizmoMove(TransformViewerSpaceToBufferSpace(event->pos()),
QVector2D(GetTexturePosition(size())), gizmo_drag_time_);
update();
} else {
// Default behavior
QOpenGLWidget::mouseMoveEvent(event);
}
}
void ViewerDisplayWidget::mouseReleaseEvent(QMouseEvent *event)
{
if (gizmo_click_) {
if (hand_dragging_) {
// Handle hand drag
emit HandDragEnded();
hand_dragging_ = false;
UpdateCursor();
} else if (gizmo_click_) {
// Handle gizmo
gizmos_->GizmoRelease();
gizmo_click_ = false;
return;
} else {
// Default behavior
QOpenGLWidget::mouseReleaseEvent(event);
}
hand_tool_clicked_ = false;
QOpenGLWidget::mouseReleaseEvent(event);
}
QMatrix4x4 ViewerDisplayWidget::GetMatrixTranslate()
@@ -355,7 +321,7 @@ void ViewerDisplayWidget::paintGL()
}
QTransform world = GenerateWorldTransform();
QTransform world_transform = GenerateWorldTransform();
// Draw gizmos if we have any
if (gizmos_) {
@@ -366,14 +332,14 @@ void ViewerDisplayWidget::paintGL()
gizmo_db_ = gt.GenerateDatabase(gizmos_, TimeRange(node_time, node_time));
QPainter p(this);
p.setWorldTransform(world);
p.setWorldTransform(world_transform);
gizmos_->DrawGizmos(gizmo_db_, &p, QVector2D(GetTexturePosition(size())), size());
}
// Draw action/title safe areas
if (safe_margin_.is_enabled()) {
QPainter p(this);
p.setWorldTransform(world);
p.setWorldTransform(world_transform);
p.setPen(Qt::lightGray);
p.setBrush(Qt::NoBrush);
@@ -399,7 +365,7 @@ void ViewerDisplayWidget::paintGL()
int cross = qMin(w, h) / 32;
QLine lines[] = {QLine(rect().center().x() - cross, rect().center().y(),rect().center().x() + cross, rect().center().y()),
QLine(rect().center().x(), rect().center().y() - cross, rect().center().x(), rect().center().y() + cross)};
QLine(rect().center().x(), rect().center().y() - cross, rect().center().x(), rect().center().y() + cross)};
p.drawLines(lines, 2);
}
@@ -426,6 +392,40 @@ rational ViewerDisplayWidget::GetGizmoTime()
return GetAdjustedTime(GetTimeTarget(), gizmos_, time_, NodeParam::kInput);
}
bool ViewerDisplayWidget::IsHandDrag(QMouseEvent *event) const
{
return event->button() == Qt::MiddleButton || Core::instance()->tool() == Tool::kHand;
}
void ViewerDisplayWidget::UpdateMatrix()
{
combined_matrix_ = scale_matrix_ * translate_matrix_;
update();
}
QTransform ViewerDisplayWidget::GenerateWorldTransform()
{
/*
* Get matrix elements (roughly) as below in column major order
*
* | Sx 0 0 Tx |
* | 0 Sy 0 Ty |
* | 0 0 Sz Tz |
* | 0 0 0 1 |
*/
float *data = combined_matrix_.data();
QTransform world;
// Move corner of canvas to correct point
world.translate(width() * 0.5 - width() * *(data)*0.5, height() * 0.5 - height() * *(data + 5) * 0.5);
// Scale
world.scale(*(data), *(data + 5));
// Translate for mouse movement
world.translate(*(data + 12) * width() * 0.5 / *(data), *(data + 13) * height() * 0.5 / *(data + 5));
return world;
}
void ViewerDisplayWidget::ContextCleanup()
{
makeCurrent();
+29 -38
View File
@@ -72,12 +72,6 @@ public:
*/
QMatrix4x4 GetMatrixTranslate();
/**
* @brief Return the complete translation and scale matrix
* This must be used if you want the entire transformation pipeline (scale and translate).
*/
QMatrix4x4 GetCompleteMatrix();
/**
* @brief Return the complete translation and scale matrix but with the Y translation flipped
* as OpenGL stores textures "upside down".
@@ -93,12 +87,6 @@ public:
FramePtr last_loaded_buffer() const;
/**
* @brief Return a QTransform that contains all the scale and translation data (inc. mouse drags)
* that can be used to set the world transform for a QPainter.
*/
QTransform GenerateWorldTransform();
/**
* @brief Transform a point from viewer space to the buffer space.
* Multiplies by the inverted transform matrix to undo the scaling and translation.
@@ -117,7 +105,7 @@ public slots:
* Set this if you want the drawing to pass through some sort of transform (most of the time you won't want this).
*/
void SetMatrixTranslate(const QMatrix4x4& mat);
/**
* @brief Set the scale matrix.
*/
@@ -140,18 +128,11 @@ public slots:
*/
void SetImage(FramePtr in_buffer);
/**
* @brief Set zoomed_ flag if the viewersizer has zoomed the image to be larger than the widget.
*
* If the image is smaller than the widget the translation is reset so the image is centered.
*/
void IsZoomed(bool flag);
/**
* @brief Changes the pointer type if the tool is changed to the hand tool. Otherwise resets the pointer to it's
* normal type.
*/
void ToolChanged(Tool::Item tool);
void UpdateCursor();
/**
* @brief Enables/disables a basic deinterlace on the viewer
@@ -164,6 +145,21 @@ signals:
*/
void DragStarted();
/**
* @brief Signal emitted when a hand drag starts
*/
void HandDragStarted();
/**
* @brief Signal emitted when a hand drag moves
*/
void HandDragMoved(int x, int y);
/**
* @brief Signal emitted when a hand drag ends
*/
void HandDragEnded();
/**
* @brief Signal emitted when cursor color is enabled and the user's mouse position changes
*/
@@ -206,6 +202,12 @@ private:
rational GetGizmoTime();
bool IsHandDrag(QMouseEvent* event) const;
void UpdateMatrix();
QTransform GenerateWorldTransform();
/**
* @brief Internal reference to the OpenGL texture to draw. Set in SetTexture() and used in paintGL().
*/
@@ -221,7 +223,10 @@ private:
*/
QMatrix4x4 scale_matrix_;
/**
* @brief Cached result of translate_matrix_ and scale_matrix_ multiplied
*/
QMatrix4x4 combined_matrix_;
bool signal_cursor_color_;
@@ -237,25 +242,11 @@ private:
FramePtr last_loaded_buffer_;
/**
* @brief Tells us if the image is zoomed in to be larger than the container widget.
*/
bool zoomed_;
/**
* @brief Position of mouse to calculate delta from.
*/
QPoint position_;
/**
* @brief Set if the hand tool is selected.
*/
bool hand_tool_;
/**
* @brief Set if the hand tool is selected and viewer is clicked.
*/
bool hand_tool_clicked_;
QPoint hand_last_drag_pos_;
bool hand_dragging_;
bool deinterlace_;
+129 -22
View File
@@ -32,6 +32,13 @@ ViewerSizer::ViewerSizer(QWidget *parent) :
pixel_aspect_(1),
zoom_(0)
{
horiz_scrollbar_ = new QScrollBar(Qt::Horizontal, this);
horiz_scrollbar_->setVisible(false);
connect(horiz_scrollbar_, &QScrollBar::valueChanged, this, &ViewerSizer::ScrollBarMoved);
vert_scrollbar_ = new QScrollBar(Qt::Vertical, this);
vert_scrollbar_->setVisible(false);
connect(vert_scrollbar_, &QScrollBar::valueChanged, this, &ViewerSizer::ScrollBarMoved);
}
void ViewerSizer::SetWidget(QWidget *widget)
@@ -70,6 +77,17 @@ void ViewerSizer::SetZoom(int percent)
UpdateSize();
}
void ViewerSizer::HandDragMove(int x, int y)
{
if (horiz_scrollbar_->isVisible()) {
horiz_scrollbar_->setValue(horiz_scrollbar_->value() - x);
}
if (vert_scrollbar_->isVisible()) {
vert_scrollbar_->setValue(vert_scrollbar_->value() - y);
}
}
void ViewerSizer::resizeEvent(QResizeEvent *event)
{
QWidget::resizeEvent(event);
@@ -94,58 +112,147 @@ void ViewerSizer::UpdateSize()
QSize child_size;
QMatrix4x4 child_matrix;
double sequence_aspect_ratio = static_cast<double>(width_) / static_cast<double>(height_) * pixel_aspect_.toDouble();
int available_width = width();
int available_height = height();
double sequence_aspect_ratio = static_cast<double>(width_) / static_cast<double>(height_)
* pixel_aspect_.toDouble();
if (zoom_ <= 0) {
// If zoom is 0, we auto-fit
double our_aspect_ratio = static_cast<double>(width()) / static_cast<double>(height());
// If zoom is zero or negative, we auto-fit
double our_aspect_ratio = static_cast<double>(available_width) / static_cast<double>(available_height);
child_size = size();
if (our_aspect_ratio > sequence_aspect_ratio) {
// This container is wider than the image, scale by height
child_size = QSize(qRound(child_size.height() * sequence_aspect_ratio), height());
child_size = QSize(qRound(child_size.height() * sequence_aspect_ratio), available_height);
} else {
// This container is taller than the image, scale by width
child_size = QSize(width(), qRound(child_size.width() / sequence_aspect_ratio));
child_size = QSize(available_width, qRound(child_size.width() / sequence_aspect_ratio));
}
emit IsZoomed(false);
// No scrollbars necessary for auto-fit
horiz_scrollbar_->setVisible(false);
vert_scrollbar_->setVisible(false);
} else {
float x_scale = 1.0f;
float y_scale = 1.0f;
// Translation is only active if the image is bigger then the widget
// See ViewerDisplayWidget::SetZoomData
bool zoomed_in = false;
int zoomed_width = qRound(width_ * static_cast<double>(zoom_) * 0.01);
int zoomed_height = qRound(height_ * static_cast<double>(zoom_) * 0.01);
int zoomed_width = GetZoomedValue(width_);
int zoomed_height = GetZoomedValue(height_);
if (zoomed_width > width()) {
x_scale = static_cast<double>(zoomed_width) / static_cast<double>(width());
zoomed_width = width();
zoomed_in = true;
bool child_exceeds_parent_width = (zoomed_width > available_width);
bool child_exceeds_parent_height = (zoomed_height > available_height);
if (child_exceeds_parent_width != child_exceeds_parent_height) {
// One scrollbar definitely needs to be shown, so it's a matter of determining if the other
// does too since adding one scrollbar necessary limits the total area
if (child_exceeds_parent_height) {
// A vertical scrollbar will need to be shown, which limits the width
child_exceeds_parent_width = (zoomed_width > available_width - vert_scrollbar_->sizeHint().width());
} else {
// A horizontal scrollbar will need to be shown, which limits the height
child_exceeds_parent_height = (zoomed_height > available_height - horiz_scrollbar_->sizeHint().height());
}
}
if (zoomed_height > height()) {
y_scale = static_cast<double>(zoomed_height) / static_cast<double>(height());
zoomed_height = height();
zoomed_in = true;
horiz_scrollbar_->setVisible(child_exceeds_parent_width);
vert_scrollbar_->setVisible(child_exceeds_parent_height);
if (vert_scrollbar_->isVisible()) {
// Limit available width for further calculations
available_width -= vert_scrollbar_->sizeHint().width();
}
if (horiz_scrollbar_->isVisible()) {
// Limit available height for further calculations
available_height -= horiz_scrollbar_->sizeHint().height();
}
if (horiz_scrollbar_->isVisible()) {
x_scale = static_cast<double>(zoomed_width) / static_cast<double>(available_width);
// Update scrollbar sizes
int horiz_width = this->width();
if (vert_scrollbar_->isVisible()) {
horiz_width -= vert_scrollbar_->sizeHint().width();
}
horiz_scrollbar_->resize(horiz_width, horiz_scrollbar_->sizeHint().height());
horiz_scrollbar_->move(0, this->height() - horiz_scrollbar_->height() - 1);
horiz_scrollbar_->setMaximum(zoomed_width - available_width);
horiz_scrollbar_->setPageStep(available_width);
zoomed_width = available_width;
}
if (vert_scrollbar_->isVisible()) {
y_scale = static_cast<double>(zoomed_height) / static_cast<double>(available_height);
// Update scrollbar sizes
int vert_height = this->height();
if (horiz_scrollbar_->isVisible()) {
vert_height -= horiz_scrollbar_->sizeHint().height();
}
vert_scrollbar_->resize(vert_scrollbar_->sizeHint().width(), vert_height);
vert_scrollbar_->move(this->width() - vert_scrollbar_->width() - 1, 0);
vert_scrollbar_->setMaximum(zoomed_height - available_height);
vert_scrollbar_->setPageStep(available_height);
zoomed_height = available_height;
}
// Rather than make a huge surface, we still crop at our width/height and then signal a matrix
child_matrix.scale(x_scale, y_scale, 1.0F);
child_size = QSize(zoomed_width, zoomed_height);
emit IsZoomed(zoomed_in);
}
widget_->resize(child_size);
widget_->move(width() / 2 - child_size.width() / 2, height() / 2 - child_size.height() / 2);
widget_->move(available_width / 2 - child_size.width() / 2,
available_height / 2 - child_size.height() / 2);
emit RequestMatrix(child_matrix);
emit RequestScale(child_matrix);
ScrollBarMoved();
}
int ViewerSizer::GetZoomedValue(int value)
{
return qRound(value * static_cast<double>(zoom_) * 0.01);
}
void ViewerSizer::ScrollBarMoved()
{
QMatrix4x4 mat;
float x_scroll, y_scroll;
if (horiz_scrollbar_->isVisible()) {
int zoomed_width = GetZoomedValue(width_);
x_scroll = (zoomed_width/2 - horiz_scrollbar_->value() - widget_->width() / 2) * (2.0 / zoomed_width);
} else {
x_scroll = 0;
}
if (vert_scrollbar_->isVisible()) {
int zoomed_height = GetZoomedValue(height_);
y_scroll = (zoomed_height/2 - vert_scrollbar_->value() - widget_->height() / 2) * (2.0 / zoomed_height);
} else {
y_scroll = 0;
}
// Zero translate is centered, so we need to determine how much "off center" we are
mat.translate(x_scroll, y_scroll);
emit RequestTranslate(mat);
}
OLIVE_NAMESPACE_EXIT
+15 -6
View File
@@ -21,6 +21,7 @@
#ifndef VIEWERSIZER_H
#define VIEWERSIZER_H
#include <QScrollBar>
#include <QWidget>
#include "common/define.h"
@@ -69,13 +70,13 @@ public:
*/
void SetZoom(int percent);
signals:
void RequestMatrix(const QMatrix4x4& matrix);
public slots:
void HandDragMove(int x, int y);
/**
* @brief Tells the viewerdisplay widget if the image is enlarged to be bigger than the widget or not.
*/
void IsZoomed(bool flag);
signals:
void RequestScale(const QMatrix4x4& matrix);
void RequestTranslate(const QMatrix4x4& matrix);
protected:
/**
@@ -89,6 +90,8 @@ private:
*/
void UpdateSize();
int GetZoomedValue(int value);
/**
* @brief Reference to widget
*
@@ -109,6 +112,12 @@ private:
*/
int zoom_;
QScrollBar* horiz_scrollbar_;
QScrollBar* vert_scrollbar_;
private slots:
void ScrollBarMoved();
};
OLIVE_NAMESPACE_EXIT