diff --git a/UI/data/locale/en-US.ini b/UI/data/locale/en-US.ini index 24feab0dc2fc69334e7965a0a5e73f8571538e6a..86b94374a521232126692602c45b4c4b8b1e02e6 100644 --- a/UI/data/locale/en-US.ini +++ b/UI/data/locale/en-US.ini @@ -330,6 +330,10 @@ Basic.MainMenu.Edit.Redo="&Redo" Basic.MainMenu.Edit.UndoAction="&Undo $1" Basic.MainMenu.Edit.RedoAction="&Redo $1" Basic.MainMenu.Edit.LockPreview="&Lock Preview" +Basic.MainMenu.Edit.Scale="Preview &Scaling" +Basic.MainMenu.Edit.Scale.Window="Scale to Window" +Basic.MainMenu.Edit.Scale.Canvas="Canvas (%1x%2)" +Basic.MainMenu.Edit.Scale.Output="Output (%1x%2)" Basic.MainMenu.Edit.Transform="&Transform" Basic.MainMenu.Edit.Transform.EditTransform="&Edit Transform..." Basic.MainMenu.Edit.Transform.ResetTransform="&Reset Transform" diff --git a/UI/display-helpers.hpp b/UI/display-helpers.hpp index d175932f3f90206d3c58e2ef5c25ffcf13d69903..27ef174ec297dce5d0069e06b6ea6ca90a242f4e 100644 --- a/UI/display-helpers.hpp +++ b/UI/display-helpers.hpp @@ -41,6 +41,14 @@ static inline void GetScaleAndCenterPos( y = windowCY/2 - newCY/2; } +static inline void GetCenterPosFromFixedScale( + int baseCX, int baseCY, int windowCX, int windowCY, + int &x, int &y, float scale) +{ + x = (float(windowCX) - float(baseCX)*scale) / 2.0f; + y = (float(windowCY) - float(baseCY)*scale) / 2.0f; +} + static inline QSize GetPixelSize(QWidget *widget) { return widget->size() * widget->devicePixelRatio(); diff --git a/UI/forms/OBSBasic.ui b/UI/forms/OBSBasic.ui index 5b922bcf8e17d9fd5492713027f6a3a3edefe0b9..041aa58970fd539f8c2c4fa71b0407cfa03ab9e8 100644 --- a/UI/forms/OBSBasic.ui +++ b/UI/forms/OBSBasic.ui @@ -873,8 +873,17 @@ + + + Basic.MainMenu.Edit.Scale + + + + + + @@ -1328,6 +1337,30 @@ Basic.MainMenu.Edit.LockPreview + + + true + + + Basic.MainMenu.Edit.Scale.Window + + + + + true + + + Basic.MainMenu.Edit.Scale.Canvas + + + + + true + + + Basic.MainMenu.Edit.Scale.Output + + diff --git a/UI/window-basic-main.cpp b/UI/window-basic-main.cpp index 01c2ae3c06d0083a496ce52daa0f47c1471c818b..9580e9b68282c8147b77ce2c013f4c7b99f0548b 100644 --- a/UI/window-basic-main.cpp +++ b/UI/window-basic-main.cpp @@ -375,6 +375,8 @@ void OBSBasic::Save(const char *file) scene, curProgramScene); obs_data_set_bool(saveData, "preview_locked", ui->preview->Locked()); + obs_data_set_int(saveData, "scaling_mode", + static_cast(ui->preview->GetScalingMode())); if (api) { obs_data_t *moduleObj = obs_data_create(); @@ -665,6 +667,19 @@ retryScene: ui->preview->SetLocked(previewLocked); ui->actionLockPreview->setChecked(previewLocked); + ScalingMode previewScaling = static_cast( + obs_data_get_int(data, "scaling_mode")); + switch (previewScaling) { + case ScalingMode::Window: + case ScalingMode::Canvas: + case ScalingMode::Output: + break; + default: + previewScaling = ScalingMode::Window; + } + + ui->preview->SetScaling(previewScaling); + if (api) { obs_data_t *modulesObj = obs_data_get_obj(data, "modules"); api->on_load(modulesObj); @@ -1567,6 +1582,17 @@ OBSSceneItem OBSBasic::GetCurrentSceneItem() return GetSceneItem(GetTopSelectedSourceItem()); } +void OBSBasic::UpdatePreviewScalingMenu() +{ + ScalingMode scalingMode = ui->preview->GetScalingMode(); + ui->actionScaleWindow->setChecked( + scalingMode == ScalingMode::Window); + ui->actionScaleCanvas->setChecked( + scalingMode == ScalingMode::Canvas); + ui->actionScaleOutput->setChecked( + scalingMode == ScalingMode::Output); +} + void OBSBasic::UpdateSources(OBSScene scene) { ClearListItems(ui->sources); @@ -2570,13 +2596,39 @@ void OBSBasic::ResetAudioDevice(const char *sourceId, const char *deviceId, void OBSBasic::ResizePreview(uint32_t cx, uint32_t cy) { QSize targetSize; + ScalingMode scalingMode; + obs_video_info ovi; /* resize preview panel to fix to the top section of the window */ targetSize = GetPixelSize(ui->preview); - GetScaleAndCenterPos(int(cx), int(cy), - targetSize.width() - PREVIEW_EDGE_SIZE * 2, - targetSize.height() - PREVIEW_EDGE_SIZE * 2, - previewX, previewY, previewScale); + + scalingMode = ui->preview->GetScalingMode(); + obs_get_video_info(&ovi); + + if (scalingMode == ScalingMode::Canvas) { + previewScale = 1.0f; + GetCenterPosFromFixedScale(int(cx), int(cy), + targetSize.width() - PREVIEW_EDGE_SIZE * 2, + targetSize.height() - PREVIEW_EDGE_SIZE * 2, + previewX, previewY, previewScale); + previewX += ui->preview->ScrollX(); + previewY += ui->preview->ScrollY(); + + } else if (scalingMode == ScalingMode::Output) { + previewScale = float(ovi.output_width) / float(ovi.base_width); + GetCenterPosFromFixedScale(int(cx), int(cy), + targetSize.width() - PREVIEW_EDGE_SIZE * 2, + targetSize.height() - PREVIEW_EDGE_SIZE * 2, + previewX, previewY, previewScale); + previewX += ui->preview->ScrollX(); + previewY += ui->preview->ScrollY(); + + } else { + GetScaleAndCenterPos(int(cx), int(cy), + targetSize.width() - PREVIEW_EDGE_SIZE * 2, + targetSize.height() - PREVIEW_EDGE_SIZE * 2, + previewX, previewY, previewScale); + } previewX += float(PREVIEW_EDGE_SIZE); previewY += float(PREVIEW_EDGE_SIZE); @@ -3083,11 +3135,8 @@ void OBSBasic::CreateSourcePopupMenu(QListWidgetItem *item, bool preview) if (IsPreviewProgramMode()) action->setEnabled(false); - action = popup.addAction( - QTStr("Basic.MainMenu.Edit.LockPreview"), - this, SLOT(on_actionLockPreview_triggered())); - action->setCheckable(true); - action->setChecked(ui->preview->Locked()); + popup.addAction(ui->actionLockPreview); + popup.addMenu(ui->scalingMenu); previewProjector = new QMenu(QTStr("PreviewProjector")); AddProjectorMenuMonitors(previewProjector, this, @@ -4565,6 +4614,45 @@ void OBSBasic::on_actionLockPreview_triggered() ui->actionLockPreview->setChecked(ui->preview->Locked()); } +void OBSBasic::on_scalingMenu_aboutToShow() +{ + obs_video_info ovi; + obs_get_video_info(&ovi); + + QAction *action = ui->actionScaleCanvas; + QString text = QTStr("Basic.MainMenu.Edit.Scale.Canvas"); + text = text.arg(QString::number(ovi.base_width), + QString::number(ovi.base_height)); + action->setText(text); + + action = ui->actionScaleOutput; + text = QTStr("Basic.MainMenu.Edit.Scale.Output"); + text = text.arg(QString::number(ovi.output_width), + QString::number(ovi.output_height)); + action->setText(text); + + UpdatePreviewScalingMenu(); +} + +void OBSBasic::on_actionScaleWindow_triggered() +{ + ui->preview->SetScaling(ScalingMode::Window); + ui->preview->ResetScrollingOffset(); + emit ui->preview->DisplayResized(); +} + +void OBSBasic::on_actionScaleCanvas_triggered() +{ + ui->preview->SetScaling(ScalingMode::Canvas); + emit ui->preview->DisplayResized(); +} + +void OBSBasic::on_actionScaleOutput_triggered() +{ + ui->preview->SetScaling(ScalingMode::Output); + emit ui->preview->DisplayResized(); +} + void OBSBasic::SetShowing(bool showing) { if (!showing && isVisible()) { diff --git a/UI/window-basic-main.hpp b/UI/window-basic-main.hpp index 3fa92d035f35decbe124b1d278076c42f85ca47b..ed477d0a45e28a29c9fb50164d85cfdc308f3354 100644 --- a/UI/window-basic-main.hpp +++ b/UI/window-basic-main.hpp @@ -200,6 +200,8 @@ private: void GetFPSNanoseconds(uint32_t &num, uint32_t &den) const; void GetConfigFPS(uint32_t &num, uint32_t &den) const; + void UpdatePreviewScalingMenu(); + void UpdateSources(OBSScene scene); void InsertSceneItem(obs_sceneitem_t *item); @@ -514,6 +516,11 @@ private slots: void on_actionLockPreview_triggered(); + void on_scalingMenu_aboutToShow(); + void on_actionScaleWindow_triggered(); + void on_actionScaleCanvas_triggered(); + void on_actionScaleOutput_triggered(); + void on_streamButton_clicked(); void on_recordButton_clicked(); void on_settingsButton_clicked(); diff --git a/UI/window-basic-preview.cpp b/UI/window-basic-preview.cpp index dc2c4f31005d745b1c33cd55f579751f1d8516f3..c265afbf14c26dd9a74f483bd60ecc40ff35de30 100644 --- a/UI/window-basic-preview.cpp +++ b/UI/window-basic-preview.cpp @@ -17,6 +17,7 @@ OBSBasicPreview::OBSBasicPreview(QWidget *parent, Qt::WindowFlags flags) : OBSQTDisplay(parent, flags) { + ResetScrollingOffset(); setMouseTracking(true); } @@ -377,6 +378,42 @@ void OBSBasicPreview::GetStretchHandleData(const vec2 &pos) } } +void OBSBasicPreview::keyPressEvent(QKeyEvent *event) +{ + if (locked || + GetScalingMode() == ScalingMode::Window || + event->isAutoRepeat()) { + OBSQTDisplay::keyPressEvent(event); + return; + } + + switch (event->key()) { + case Qt::Key_Space: + setCursor(Qt::OpenHandCursor); + scrollMode = true; + break; + } + + OBSQTDisplay::keyPressEvent(event); +} + +void OBSBasicPreview::keyReleaseEvent(QKeyEvent *event) +{ + if (event->isAutoRepeat()) { + OBSQTDisplay::keyReleaseEvent(event); + return; + } + + switch (event->key()) { + case Qt::Key_Space: + scrollMode = false; + setCursor(Qt::ArrowCursor); + break; + } + + OBSQTDisplay::keyReleaseEvent(event); +} + void OBSBasicPreview::mousePressEvent(QMouseEvent *event) { if (locked) { @@ -393,6 +430,13 @@ void OBSBasicPreview::mousePressEvent(QMouseEvent *event) OBSQTDisplay::mousePressEvent(event); + if (scrollMode && GetScalingMode() != ScalingMode::Window) { + setCursor(Qt::ClosedHandCursor); + scrollingFrom.x = event->x(); + scrollingFrom.y = event->y(); + return; + } + if (event->button() != Qt::LeftButton && event->button() != Qt::RightButton) return; @@ -461,6 +505,9 @@ void OBSBasicPreview::mouseReleaseEvent(QMouseEvent *event) return; } + if (scrollMode) + setCursor(Qt::OpenHandCursor); + if (mouseDown) { vec2 pos = GetMouseEventPos(event); @@ -954,6 +1001,15 @@ void OBSBasicPreview::mouseMoveEvent(QMouseEvent *event) if (locked) return; + if (scrollMode && event->buttons() == Qt::LeftButton) { + scrollingOffset.x += event->x() - scrollingFrom.x; + scrollingOffset.y += event->y() - scrollingFrom.y; + scrollingFrom.x = event->x(); + scrollingFrom.y = event->y(); + emit DisplayResized(); + return; + } + if (mouseDown) { vec2 pos = GetMouseEventPos(event); @@ -1113,3 +1169,8 @@ void OBSBasicPreview::DrawSceneEditing() gs_technique_end_pass(tech); gs_technique_end(tech); } + +void OBSBasicPreview::ResetScrollingOffset() +{ + vec2_zero(&scrollingOffset); +} diff --git a/UI/window-basic-preview.hpp b/UI/window-basic-preview.hpp index c11581b670da6b00a941622c26c32f59991f0b44..fed3c1885f5c9a3b9aeb5802c3413fb20796be93 100644 --- a/UI/window-basic-preview.hpp +++ b/UI/window-basic-preview.hpp @@ -26,6 +26,12 @@ enum class ItemHandle : uint32_t { BottomRight = ITEM_BOTTOM | ITEM_RIGHT }; +enum class ScalingMode : uint32_t { + Window = 0, + Canvas = 1, + Output = 2 +}; + class OBSBasicPreview : public OBSQTDisplay { Q_OBJECT @@ -35,17 +41,21 @@ private: vec2 cropSize; OBSSceneItem stretchItem; ItemHandle stretchHandle = ItemHandle::None; + ScalingMode scale = ScalingMode::Window; vec2 stretchItemSize; matrix4 screenToItem; matrix4 itemToScreen; vec2 startPos; vec2 lastMoveOffset; + vec2 scrollingFrom; + vec2 scrollingOffset; bool mouseDown = false; bool mouseMoved = false; bool mouseOverItems = false; bool cropping = false; bool locked = false; + bool scrollMode = false; static vec2 GetMouseEventPos(QMouseEvent *event); static bool DrawSelectedItem(obs_scene_t *scene, obs_sceneitem_t *item, @@ -75,16 +85,26 @@ private: public: OBSBasicPreview(QWidget *parent, Qt::WindowFlags flags = 0); + virtual void keyPressEvent(QKeyEvent *event) override; + virtual void keyReleaseEvent(QKeyEvent *event) override; + virtual void mousePressEvent(QMouseEvent *event) override; virtual void mouseReleaseEvent(QMouseEvent *event) override; virtual void mouseMoveEvent(QMouseEvent *event) override; void DrawSceneEditing(); + void ResetScrollingOffset(); inline void SetLocked(bool newLockedVal) {locked = newLockedVal;} inline void ToggleLocked() {locked = !locked;} inline bool Locked() const {return locked;} + inline void SetScaling(ScalingMode newScaledVal) {scale = newScaledVal;} + inline ScalingMode GetScalingMode() const {return scale;} + + inline float ScrollX() const {return scrollingOffset.x;} + inline float ScrollY() const {return scrollingOffset.y;} + /* use libobs allocator for alignment because the matrices itemToScreen * and screenToItem may contain SSE data, which will cause SSE * instructions to crash if the data is not aligned to at least a 16