frontend: Update transform dialog

This commit is contained in:
Warchamp7
2025-07-19 19:09:25 -04:00
committed by Ryan Foster
parent c01a9bea49
commit 6fa1a35ad4
6 changed files with 941 additions and 723 deletions

View File

@@ -4,6 +4,7 @@
#include "moc_OBSBasicTransform.cpp"
namespace {
static bool find_sel(obs_scene_t *, obs_sceneitem_t *item, void *param)
{
OBSSceneItem &dst = *static_cast<OBSSceneItem *>(param);
@@ -28,8 +29,28 @@ static OBSSceneItem FindASelectedItem(obs_scene_t *scene)
obs_scene_enum_items(scene, find_sel, &item);
return item;
}
static vec2 getAlignmentConversion(uint32_t alignment)
{
vec2 ratio = {0.5f, 0.5f};
if (alignment & OBS_ALIGN_RIGHT) {
ratio.x = 1.0f;
}
if (alignment & OBS_ALIGN_LEFT) {
ratio.x = 0.0f;
}
if (alignment & OBS_ALIGN_BOTTOM) {
ratio.y = 1.0f;
}
if (alignment & OBS_ALIGN_TOP) {
ratio.y = 0.0f;
}
return ratio;
}
} // namespace
#define COMBO_CHANGED &QComboBox::currentIndexChanged
#define ALIGN_CHANGED &AlignmentSelector::currentIndexChanged
#define ISCROLL_CHANGED &QSpinBox::valueChanged
#define DSCROLL_CHANGED &QDoubleSpinBox::valueChanged
@@ -42,24 +63,38 @@ OBSBasicTransform::OBSBasicTransform(OBSSceneItem item, OBSBasic *parent)
ui->setupUi(this);
HookWidget(ui->positionX, DSCROLL_CHANGED, &OBSBasicTransform::OnControlChanged);
HookWidget(ui->positionY, DSCROLL_CHANGED, &OBSBasicTransform::OnControlChanged);
HookWidget(ui->rotation, DSCROLL_CHANGED, &OBSBasicTransform::OnControlChanged);
HookWidget(ui->sizeX, DSCROLL_CHANGED, &OBSBasicTransform::OnControlChanged);
HookWidget(ui->sizeY, DSCROLL_CHANGED, &OBSBasicTransform::OnControlChanged);
HookWidget(ui->align, COMBO_CHANGED, &OBSBasicTransform::OnControlChanged);
HookWidget(ui->boundsType, COMBO_CHANGED, &OBSBasicTransform::OnBoundsType);
HookWidget(ui->boundsAlign, COMBO_CHANGED, &OBSBasicTransform::OnControlChanged);
HookWidget(ui->boundsWidth, DSCROLL_CHANGED, &OBSBasicTransform::OnControlChanged);
HookWidget(ui->boundsHeight, DSCROLL_CHANGED, &OBSBasicTransform::OnControlChanged);
HookWidget(ui->cropLeft, ISCROLL_CHANGED, &OBSBasicTransform::OnCropChanged);
HookWidget(ui->cropRight, ISCROLL_CHANGED, &OBSBasicTransform::OnCropChanged);
HookWidget(ui->cropTop, ISCROLL_CHANGED, &OBSBasicTransform::OnCropChanged);
HookWidget(ui->cropBottom, ISCROLL_CHANGED, &OBSBasicTransform::OnCropChanged);
positionAlignment = new AlignmentSelector(this);
positionAlignment->setAccessibleName(QTStr("Basic.TransformWindow.Alignment"));
ui->alignmentLayout->addWidget(positionAlignment);
positionAlignment->setAlignment(Qt::AlignTop | Qt::AlignLeft);
boundsAlignment = new AlignmentSelector(this);
boundsAlignment->setAccessibleName(QTStr("Basic.TransformWindow.BoundsAlignment"));
boundsAlignment->setEnabled(false);
ui->boundsAlignmentLayout->addWidget(boundsAlignment);
boundsAlignment->setAlignment(Qt::AlignTop | Qt::AlignLeft);
setTabOrder(ui->rotation, positionAlignment);
setTabOrder(ui->boundsType, boundsAlignment);
hookWidget(ui->positionX, DSCROLL_CHANGED, &OBSBasicTransform::onControlChanged);
hookWidget(ui->positionY, DSCROLL_CHANGED, &OBSBasicTransform::onControlChanged);
hookWidget(ui->rotation, DSCROLL_CHANGED, &OBSBasicTransform::onControlChanged);
hookWidget(ui->sizeX, DSCROLL_CHANGED, &OBSBasicTransform::onControlChanged);
hookWidget(ui->sizeY, DSCROLL_CHANGED, &OBSBasicTransform::onControlChanged);
hookWidget(positionAlignment.get(), ALIGN_CHANGED, &OBSBasicTransform::onAlignChanged);
hookWidget(ui->boundsType, COMBO_CHANGED, &OBSBasicTransform::onBoundsType);
hookWidget(boundsAlignment.get(), ALIGN_CHANGED, &OBSBasicTransform::onControlChanged);
hookWidget(ui->boundsWidth, DSCROLL_CHANGED, &OBSBasicTransform::onControlChanged);
hookWidget(ui->boundsHeight, DSCROLL_CHANGED, &OBSBasicTransform::onControlChanged);
hookWidget(ui->cropLeft, ISCROLL_CHANGED, &OBSBasicTransform::onCropChanged);
hookWidget(ui->cropRight, ISCROLL_CHANGED, &OBSBasicTransform::onCropChanged);
hookWidget(ui->cropTop, ISCROLL_CHANGED, &OBSBasicTransform::onCropChanged);
hookWidget(ui->cropBottom, ISCROLL_CHANGED, &OBSBasicTransform::onCropChanged);
#if QT_VERSION >= QT_VERSION_CHECK(6, 7, 0)
HookWidget(ui->cropToBounds, &QCheckBox::checkStateChanged, &OBSBasicTransform::OnControlChanged);
hookWidget(ui->cropToBounds, &QCheckBox::checkStateChanged, &OBSBasicTransform::onControlChanged);
#else
HookWidget(ui->cropToBounds, &QCheckBox::stateChanged, &OBSBasicTransform::OnControlChanged);
hookWidget(ui->cropToBounds, &QCheckBox::stateChanged, &OBSBasicTransform::onControlChanged);
#endif
ui->buttonBox->button(QDialogButtonBox::Close)->setDefault(true);
@@ -69,14 +104,18 @@ OBSBasicTransform::OBSBasicTransform(OBSSceneItem item, OBSBasic *parent)
installEventFilter(CreateShortcutFilter());
OBSScene scene = obs_sceneitem_get_scene(item);
SetScene(scene);
SetItem(item);
setScene(scene);
setItem(item);
std::string name = obs_source_get_name(obs_sceneitem_get_source(item));
setWindowTitle(QTStr("Basic.TransformWindow.Title").arg(name.c_str()));
OBSDataAutoRelease wrapper = obs_scene_save_transform_states(main->GetCurrentScene(), false);
undo_data = std::string(obs_data_get_json(wrapper));
adjustSize();
setMinimumSize(size());
setMaximumSize(size());
}
OBSBasicTransform::~OBSBasicTransform()
@@ -97,7 +136,7 @@ OBSBasicTransform::~OBSBasicTransform()
undo_redo, undo_redo, undo_data, redo_data);
}
void OBSBasicTransform::SetScene(OBSScene scene)
void OBSBasicTransform::setScene(OBSScene scene)
{
sigs.clear();
@@ -113,25 +152,27 @@ void OBSBasicTransform::SetScene(OBSScene scene)
}
}
void OBSBasicTransform::SetItem(OBSSceneItem newItem)
void OBSBasicTransform::setItem(OBSSceneItem newItem)
{
QMetaObject::invokeMethod(this, "SetItemQt", Q_ARG(OBSSceneItem, OBSSceneItem(newItem)));
QMetaObject::invokeMethod(this, "setItemQt", Q_ARG(OBSSceneItem, OBSSceneItem(newItem)));
}
void OBSBasicTransform::SetEnabled(bool enable)
void OBSBasicTransform::setEnabled(bool enable)
{
ui->container->setEnabled(enable);
ui->transformSettings->setEnabled(enable);
ui->boundsSettings->setEnabled(enable);
ui->cropSettings->setEnabled(enable);
ui->buttonBox->button(QDialogButtonBox::Reset)->setEnabled(enable);
}
void OBSBasicTransform::SetItemQt(OBSSceneItem newItem)
void OBSBasicTransform::setItemQt(OBSSceneItem newItem)
{
item = newItem;
if (item)
RefreshControls();
refreshControls();
bool enable = !!item && !obs_sceneitem_locked(item);
SetEnabled(enable);
setEnabled(enable);
}
void OBSBasicTransform::OBSSceneItemTransform(void *param, calldata_t *data)
@@ -140,7 +181,7 @@ void OBSBasicTransform::OBSSceneItemTransform(void *param, calldata_t *data)
OBSSceneItem item = (obs_sceneitem_t *)calldata_ptr(data, "item");
if (item == window->item && !window->ignoreTransformSignal)
QMetaObject::invokeMethod(window, "RefreshControls");
QMetaObject::invokeMethod(window, "refreshControls");
}
void OBSBasicTransform::OBSSceneItemRemoved(void *param, calldata_t *data)
@@ -150,7 +191,7 @@ void OBSBasicTransform::OBSSceneItemRemoved(void *param, calldata_t *data)
obs_sceneitem_t *item = (obs_sceneitem_t *)calldata_ptr(data, "item");
if (item == window->item)
window->SetItem(FindASelectedItem(scene));
window->setItem(FindASelectedItem(scene));
}
void OBSBasicTransform::OBSSceneItemSelect(void *param, calldata_t *data)
@@ -159,7 +200,7 @@ void OBSBasicTransform::OBSSceneItemSelect(void *param, calldata_t *data)
OBSSceneItem item = (obs_sceneitem_t *)calldata_ptr(data, "item");
if (item != window->item)
window->SetItem(item);
window->setItem(item);
}
void OBSBasicTransform::OBSSceneItemDeselect(void *param, calldata_t *data)
@@ -170,7 +211,7 @@ void OBSBasicTransform::OBSSceneItemDeselect(void *param, calldata_t *data)
if (item == window->item) {
window->setWindowTitle(QTStr("Basic.TransformWindow.NoSelectedSource"));
window->SetItem(FindASelectedItem(scene));
window->setItem(FindASelectedItem(scene));
}
}
@@ -179,23 +220,23 @@ void OBSBasicTransform::OBSSceneItemLocked(void *param, calldata_t *data)
OBSBasicTransform *window = static_cast<OBSBasicTransform *>(param);
bool locked = calldata_bool(data, "locked");
QMetaObject::invokeMethod(window, "SetEnabled", Q_ARG(bool, !locked));
QMetaObject::invokeMethod(window, "setEnabled", Q_ARG(bool, !locked));
}
static const uint32_t listToAlign[] = {OBS_ALIGN_TOP | OBS_ALIGN_LEFT,
OBS_ALIGN_TOP,
OBS_ALIGN_TOP | OBS_ALIGN_RIGHT,
OBS_ALIGN_LEFT,
OBS_ALIGN_CENTER,
OBS_ALIGN_RIGHT,
OBS_ALIGN_BOTTOM | OBS_ALIGN_LEFT,
OBS_ALIGN_BOTTOM,
OBS_ALIGN_BOTTOM | OBS_ALIGN_RIGHT};
static const uint32_t indexToAlign[] = {OBS_ALIGN_TOP | OBS_ALIGN_LEFT,
OBS_ALIGN_TOP,
OBS_ALIGN_TOP | OBS_ALIGN_RIGHT,
OBS_ALIGN_LEFT,
OBS_ALIGN_CENTER,
OBS_ALIGN_RIGHT,
OBS_ALIGN_BOTTOM | OBS_ALIGN_LEFT,
OBS_ALIGN_BOTTOM,
OBS_ALIGN_BOTTOM | OBS_ALIGN_RIGHT};
static int AlignToList(uint32_t align)
static int alignToIndex(uint32_t align)
{
int index = 0;
for (uint32_t curAlign : listToAlign) {
for (uint32_t curAlign : indexToAlign) {
if (curAlign == align)
return index;
@@ -205,14 +246,14 @@ static int AlignToList(uint32_t align)
return 0;
}
void OBSBasicTransform::RefreshControls()
void OBSBasicTransform::refreshControls()
{
if (!item)
return;
obs_transform_info osi;
obs_transform_info oti;
obs_sceneitem_crop crop;
obs_sceneitem_get_info2(item, &osi);
obs_sceneitem_get_info2(item, &oti);
obs_sceneitem_get_crop(item, &crop);
obs_source_t *source = obs_sceneitem_get_source(item);
@@ -221,26 +262,26 @@ void OBSBasicTransform::RefreshControls()
float width = float(source_cx);
float height = float(source_cy);
int alignIndex = AlignToList(osi.alignment);
int boundsAlignIndex = AlignToList(osi.bounds_alignment);
int alignIndex = alignToIndex(oti.alignment);
int boundsAlignIndex = alignToIndex(oti.bounds_alignment);
ignoreItemChange = true;
ui->positionX->setValue(osi.pos.x);
ui->positionY->setValue(osi.pos.y);
ui->rotation->setValue(osi.rot);
ui->sizeX->setValue(osi.scale.x * width);
ui->sizeY->setValue(osi.scale.y * height);
ui->align->setCurrentIndex(alignIndex);
ui->positionX->setValue(oti.pos.x);
ui->positionY->setValue(oti.pos.y);
ui->rotation->setValue(oti.rot);
ui->sizeX->setValue(oti.scale.x * width);
ui->sizeY->setValue(oti.scale.y * height);
positionAlignment->setCurrentIndex(alignIndex);
bool valid_size = source_cx != 0 && source_cy != 0;
ui->sizeX->setEnabled(valid_size);
ui->sizeY->setEnabled(valid_size);
ui->boundsType->setCurrentIndex(int(osi.bounds_type));
ui->boundsAlign->setCurrentIndex(boundsAlignIndex);
ui->boundsWidth->setValue(osi.bounds.x);
ui->boundsHeight->setValue(osi.bounds.y);
ui->cropToBounds->setChecked(osi.crop_to_bounds);
ui->boundsType->setCurrentIndex(int(oti.bounds_type));
boundsAlignment->setCurrentIndex(boundsAlignIndex);
ui->boundsWidth->setValue(oti.bounds.x);
ui->boundsHeight->setValue(oti.bounds.y);
ui->cropToBounds->setChecked(oti.crop_to_bounds);
ui->cropLeft->setValue(int(crop.left));
ui->cropRight->setValue(int(crop.right));
@@ -252,7 +293,45 @@ void OBSBasicTransform::RefreshControls()
setWindowTitle(QTStr("Basic.TransformWindow.Title").arg(name.c_str()));
}
void OBSBasicTransform::OnBoundsType(int index)
void OBSBasicTransform::onAlignChanged(int index)
{
uint32_t alignment = indexToAlign[index];
vec2 flipRatio = getAlignmentConversion(alignment);
obs_transform_info oti;
obs_sceneitem_crop crop;
obs_sceneitem_get_info2(item, &oti);
obs_sceneitem_get_crop(item, &crop);
obs_source_t *source = obs_sceneitem_get_source(item);
uint32_t sourceWidth = obs_source_get_width(source);
uint32_t sourceHeight = obs_source_get_height(source);
uint32_t widthForFlip = sourceWidth - crop.left - crop.right;
uint32_t heightForFlip = sourceHeight - crop.top - crop.bottom;
if (oti.bounds_type != OBS_BOUNDS_NONE) {
widthForFlip = oti.bounds.x;
heightForFlip = oti.bounds.y;
}
vec2 currentRatio = getAlignmentConversion(oti.alignment);
float shiftX = (currentRatio.x - flipRatio.x) * widthForFlip * oti.scale.x;
float shiftY = (currentRatio.y - flipRatio.y) * heightForFlip * oti.scale.y;
bool previousIgnoreState = ignoreItemChange;
ignoreItemChange = true;
ui->positionX->setValue(oti.pos.x - shiftX);
ui->positionY->setValue(oti.pos.y - shiftY);
ignoreItemChange = previousIgnoreState;
onControlChanged();
}
void OBSBasicTransform::onBoundsType(int index)
{
if (index == -1)
return;
@@ -260,10 +339,13 @@ void OBSBasicTransform::OnBoundsType(int index)
obs_bounds_type type = (obs_bounds_type)index;
bool enable = (type != OBS_BOUNDS_NONE);
ui->boundsAlign->setEnabled(enable);
boundsAlignment->setEnabled(enable && type != OBS_BOUNDS_STRETCH);
ui->boundsWidth->setEnabled(enable);
ui->boundsHeight->setEnabled(enable);
ui->cropToBounds->setEnabled(enable);
bool isCoverBounds = type == OBS_BOUNDS_SCALE_OUTER || type == OBS_BOUNDS_SCALE_TO_WIDTH ||
type == OBS_BOUNDS_SCALE_TO_HEIGHT;
ui->cropToBounds->setEnabled(isCoverBounds);
if (!ignoreItemChange) {
obs_bounds_type lastType = obs_sceneitem_get_bounds_type(item);
@@ -272,15 +354,56 @@ void OBSBasicTransform::OnBoundsType(int index)
int width = (int)obs_source_get_width(source);
int height = (int)obs_source_get_height(source);
ui->boundsWidth->setValue(width);
ui->boundsHeight->setValue(height);
vec2 scale;
obs_sceneitem_get_scale(item, &scale);
obs_sceneitem_crop crop;
obs_sceneitem_get_crop(item, &crop);
ui->sizeX->setValue(width);
ui->sizeY->setValue(height);
ui->boundsWidth->setValue((width - crop.left - crop.right) * scale.x);
ui->boundsHeight->setValue((height - crop.top - crop.bottom) * scale.y);
} else if (type == OBS_BOUNDS_NONE) {
OBSSource source = obs_sceneitem_get_source(item);
int width = (int)obs_source_get_width(source);
int height = (int)obs_source_get_height(source);
matrix4 draw;
obs_sceneitem_get_draw_transform(item, &draw);
ui->sizeX->setValue(width * draw.x.x);
ui->sizeY->setValue(height * draw.y.y);
obs_transform_info oti;
obs_sceneitem_get_info2(item, &oti);
// We use the draw transform values here which is always a top left coordinate origin.
vec2 currentRatio = getAlignmentConversion(OBS_ALIGN_TOP | OBS_ALIGN_LEFT);
vec2 flipRatio = getAlignmentConversion(oti.alignment);
float drawX = draw.t.x;
float drawY = draw.t.y;
obs_sceneitem_crop crop;
obs_sceneitem_get_crop(item, &crop);
uint32_t widthForFlip = width - crop.left - crop.right;
uint32_t heightForFlip = height - crop.top - crop.bottom;
float shiftX = (currentRatio.x - flipRatio.x) * (widthForFlip * draw.x.x);
float shiftY = (currentRatio.y - flipRatio.y) * (heightForFlip * draw.y.y);
ui->positionX->setValue(oti.pos.x - (oti.pos.x - drawX) - shiftX);
ui->positionY->setValue(oti.pos.y - (oti.pos.y - drawY) - shiftY);
}
}
OnControlChanged();
onControlChanged();
}
void OBSBasicTransform::OnControlChanged()
void OBSBasicTransform::onControlChanged()
{
if (ignoreItemChange)
return;
@@ -303,10 +426,10 @@ void OBSBasicTransform::OnControlChanged()
oti.pos.x = float(ui->positionX->value());
oti.pos.y = float(ui->positionY->value());
oti.rot = float(ui->rotation->value());
oti.alignment = listToAlign[ui->align->currentIndex()];
oti.alignment = indexToAlign[positionAlignment->currentIndex()];
oti.bounds_type = (obs_bounds_type)ui->boundsType->currentIndex();
oti.bounds_alignment = listToAlign[ui->boundsAlign->currentIndex()];
oti.bounds_alignment = indexToAlign[boundsAlignment->currentIndex()];
oti.bounds.x = float(ui->boundsWidth->value());
oti.bounds.y = float(ui->boundsHeight->value());
oti.crop_to_bounds = ui->cropToBounds->isChecked();
@@ -316,7 +439,7 @@ void OBSBasicTransform::OnControlChanged()
ignoreTransformSignal = false;
}
void OBSBasicTransform::OnCropChanged()
void OBSBasicTransform::onCropChanged()
{
if (ignoreItemChange)
return;
@@ -332,11 +455,11 @@ void OBSBasicTransform::OnCropChanged()
ignoreTransformSignal = false;
}
void OBSBasicTransform::OnSceneChanged(QListWidgetItem *current, QListWidgetItem *)
void OBSBasicTransform::onSceneChanged(QListWidgetItem *current, QListWidgetItem *)
{
if (!current)
return;
OBSScene scene = GetOBSRef<OBSScene>(current);
this->SetScene(scene);
this->setScene(scene);
}