diff --git a/app/widget/viewer/viewer.cpp b/app/widget/viewer/viewer.cpp index c271862f1..4af7869b6 100644 --- a/app/widget/viewer/viewer.cpp +++ b/app/widget/viewer/viewer.cpp @@ -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 diff --git a/app/widget/viewer/viewerdisplay.cpp b/app/widget/viewer/viewerdisplay.cpp index 8f243503a..aee82eaed 100644 --- a/app/widget/viewer/viewerdisplay.cpp +++ b/app/widget/viewer/viewerdisplay.cpp @@ -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(event->y()) / static_cast(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(); diff --git a/app/widget/viewer/viewerdisplay.h b/app/widget/viewer/viewerdisplay.h index bbaf4f45f..0f8f6866f 100644 --- a/app/widget/viewer/viewerdisplay.h +++ b/app/widget/viewer/viewerdisplay.h @@ -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_; diff --git a/app/widget/viewer/viewersizer.cpp b/app/widget/viewer/viewersizer.cpp index 5665ff6b6..33a1f211c 100644 --- a/app/widget/viewer/viewersizer.cpp +++ b/app/widget/viewer/viewersizer.cpp @@ -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(width_) / static_cast(height_) * pixel_aspect_.toDouble(); + int available_width = width(); + int available_height = height(); + double sequence_aspect_ratio = static_cast(width_) / static_cast(height_) + * pixel_aspect_.toDouble(); if (zoom_ <= 0) { - // If zoom is 0, we auto-fit - double our_aspect_ratio = static_cast(width()) / static_cast(height()); + // If zoom is zero or negative, we auto-fit + double our_aspect_ratio = static_cast(available_width) / static_cast(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(zoom_) * 0.01); - int zoomed_height = qRound(height_ * static_cast(zoom_) * 0.01); + int zoomed_width = GetZoomedValue(width_); + int zoomed_height = GetZoomedValue(height_); - if (zoomed_width > width()) { - x_scale = static_cast(zoomed_width) / static_cast(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(zoomed_height) / static_cast(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(zoomed_width) / static_cast(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(zoomed_height) / static_cast(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(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 diff --git a/app/widget/viewer/viewersizer.h b/app/widget/viewer/viewersizer.h index efe6803e3..44601e6c6 100644 --- a/app/widget/viewer/viewersizer.h +++ b/app/widget/viewer/viewersizer.h @@ -21,6 +21,7 @@ #ifndef VIEWERSIZER_H #define VIEWERSIZER_H +#include #include #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