diff --git a/UI/data/themes/Acri.qss b/UI/data/themes/Acri.qss index e248cd005..bed8b540f 100644 --- a/UI/data/themes/Acri.qss +++ b/UI/data/themes/Acri.qss @@ -1049,3 +1049,36 @@ SceneTree#scenes { * [themeID="replayIconSmall"] { qproperty-icon: url(./Dark/save.svg); } + +/* Studio Mode T-Bar */ + +QSlider[themeID="tBarSlider"] { + height: 80px; +} + +QSlider::groove:horizontal[themeID="tBarSlider"] { + height: 5px; + margin: 0 12px; + background-color: QLinearGradient(x1: 0, y1: 0, x2: 0, y2: 1, + stop: 0 rgb(31,30,31), + stop: 0.75 rgb(50, 49, 50)); + border: none; + border-radius: 2px; +} + +QSlider::sub-page:horizontal[themeID="tBarSlider"] { + height: 5px; + margin: 0 12px; + background-color: QLinearGradient(x1: 0, y1: 0, x2: 0, y2: 1, + stop: 0 rgb(31,30,31), + stop: 0.75 rgb(50, 49, 50)); + border: none; + border-radius: 2px; +} + +QSlider::handle:horizontal[themeID="tBarSlider"] { + background-color: #d2d2d2; + width: 25px; + height: 80px; + margin: -24px -12px; +} diff --git a/UI/data/themes/Dark.qss b/UI/data/themes/Dark.qss index f034f1bf4..a231b2dc8 100644 --- a/UI/data/themes/Dark.qss +++ b/UI/data/themes/Dark.qss @@ -810,3 +810,28 @@ SceneTree { * [themeID="replayIconSmall"] { qproperty-icon: url(./Dark/save.svg); } + +/* Studio Mode T-Bar */ + +QSlider[themeID="tBarSlider"] { + height: 80px; +} + +QSlider::groove:horizontal[themeID="tBarSlider"] { + border: 1px solid #4c4c4c; + height: 5px; + background: rgb(31,30,31);; + margin: 0 12px; +} + +QSlider::sub-page:horizontal[themeID="tBarSlider"] { + background: rgb(31,30,31);; + border: 1px solid #4c4c4c; +} + +QSlider::handle:horizontal[themeID="tBarSlider"] { + background-color: #d2d2d2; + width: 25px; + height: 80px; + margin: -24px -12px; +} diff --git a/UI/data/themes/Rachni.qss b/UI/data/themes/Rachni.qss index 08a87a2f1..7a32af16b 100644 --- a/UI/data/themes/Rachni.qss +++ b/UI/data/themes/Rachni.qss @@ -1373,3 +1373,36 @@ SceneTree#scenes { * [themeID="replayIconSmall"] { qproperty-icon: url(./Dark/save.svg); } + +/* Studio Mode T-Bar */ + +QSlider[themeID="tBarSlider"] { + height: 80px; +} + +QSlider::groove:horizontal[themeID="tBarSlider"] { + height: 5px; + margin: 0 12px; + background-color: QLinearGradient(x1: 1, y1: 0, x2: 0, y2: 0, + stop: 0 rgb(35, 38, 41), /* Dark Gray */ + stop: 0.75 rgb(50, 49, 50)); + border: none; + border-radius: 2px; +} + +QSlider::sub-page:horizontal[themeID="tBarSlider"] { + height: 5px; + margin: 0 12px; + background-color: QLinearGradient(x1: 1, y1: 0, x2: 0, y2: 0, + stop: 0 rgb(35, 38, 41), /* Dark Gray */ + stop: 0.75 rgb(50, 49, 50)); + border: none; + border-radius: 2px; +} + +QSlider::handle:horizontal[themeID="tBarSlider"] { + background-color: #d2d2d2; + width: 25px; + height: 80px; + margin: -24px -12px; +} diff --git a/UI/data/themes/System.qss b/UI/data/themes/System.qss index 1ff5febf8..7833730dd 100644 --- a/UI/data/themes/System.qss +++ b/UI/data/themes/System.qss @@ -241,3 +241,29 @@ SceneTree { * [themeID="replayIconSmall"] { qproperty-icon: url(:res/images/save.svg); } + +/* Studio Mode T-Bar */ + +QSlider[themeID="tBarSlider"] { + height: 80px; +} + +QSlider::groove:horizontal[themeID="tBarSlider"] { + border: 1px solid #4c4c4c; + height: 5px; + background: #DCD9D7; + margin: 0 12px; +} + +QSlider::sub-page:horizontal[themeID="tBarSlider"] { + background: #DCD9D7; + border: 1px solid #4c4c4c; +} + +QSlider::handle:horizontal[themeID="tBarSlider"] { + background-color: #f3f3f3; + border: 1px solid #4c4c4c; + width: 25px; + height: 80px; + margin: -24px -12px; +} diff --git a/UI/window-basic-main-transitions.cpp b/UI/window-basic-main-transitions.cpp index 42f275fd6..28ff4ee9d 100644 --- a/UI/window-basic-main-transitions.cpp +++ b/UI/window-basic-main-transitions.cpp @@ -306,7 +306,7 @@ void OBSBasic::TransitionFullyStopped() void OBSBasic::TransitionToScene(OBSSource source, bool force, bool quickTransition, int quickDuration, - bool black) + bool black, bool manual) { obs_scene_t *scene = obs_scene_from_source(source); bool usingPreviewProgram = IsPreviewProgramMode(); @@ -316,7 +316,8 @@ void OBSBasic::TransitionToScene(OBSSource source, bool force, OBSWeakSource lastProgramScene; if (usingPreviewProgram) { - lastProgramScene = programScene; + if (!tBarActive) + lastProgramScene = programScene; programScene = OBSGetWeakRef(source); if (swapScenesMode && !force && !black) { @@ -341,12 +342,16 @@ void OBSBasic::TransitionToScene(OBSSource source, bool force, OBSSource transition = obs_get_output_source(0); obs_source_release(transition); - bool stillTransitioning = obs_transition_get_time(transition) < 1.0f; + float t = obs_transition_get_time(transition); + bool stillTransitioning = (!tBarActive && t < 1.0f) || + (tBarActive && t > 0.0f) || tBarDown; // If actively transitioning, block new transitions from starting if (usingPreviewProgram && stillTransitioning) goto cleanup; + tBarActive = false; + if (force) { obs_transition_set(transition, source); if (api) @@ -390,8 +395,13 @@ void OBSBasic::TransitionToScene(OBSSource source, bool force, if (quickTransition) duration = quickDuration; - bool success = obs_transition_start( - transition, OBS_TRANSITION_MODE_AUTO, duration, source); + enum obs_transition_mode mode = + manual ? OBS_TRANSITION_MODE_MANUAL + : OBS_TRANSITION_MODE_AUTO; + + bool success = obs_transition_start(transition, mode, duration, + source); + if (!success) TransitionFullyStopped(); } @@ -444,6 +454,8 @@ void OBSBasic::SetTransition(OBSSource transition) ui->transitionRemove->setEnabled(configurable); ui->transitionProps->setEnabled(configurable); + EnableTBar(); + if (api) api->on_event(OBS_FRONTEND_EVENT_TRANSITION_CHANGED); } @@ -765,6 +777,10 @@ void OBSBasic::TransitionClicked() TransitionToScene(GetCurrentScene()); } +#define T_BAR_PRECISION 1024 +#define T_BAR_PRECISION_F ((float)T_BAR_PRECISION) +#define T_BAR_CLAMP (T_BAR_PRECISION / 10) + void OBSBasic::CreateProgramOptions() { programOptions = new QWidget(); @@ -796,9 +812,19 @@ void OBSBasic::CreateProgramOptions() mainButtonLayout->addWidget(transitionButton); mainButtonLayout->addWidget(configTransitions); + tBar = new QSlider(Qt::Horizontal); + tBar->setMinimum(0); + tBar->setMaximum(T_BAR_PRECISION - 1); + + tBar->setProperty("themeID", "tBarSlider"); + + connect(tBar, SIGNAL(sliderMoved(int)), this, SLOT(TBarChanged(int))); + connect(tBar, SIGNAL(sliderReleased()), this, SLOT(TBarReleased())); + layout->addStretch(0); layout->addLayout(mainButtonLayout); layout->addLayout(quickTransitions); + layout->addWidget(tBar); layout->addStretch(0); programOptions->setLayout(layout); @@ -871,6 +897,58 @@ void OBSBasic::CreateProgramOptions() connect(configTransitions, &QAbstractButton::clicked, onConfig); } +void OBSBasic::TBarReleased() +{ + int val = tBar->value(); + + if ((tBar->maximum() - val) <= T_BAR_CLAMP) { + obs_transition_set_manual_time(GetCurrentTransition(), 1.0f); + tBar->blockSignals(true); + tBar->setValue(0); + tBar->blockSignals(false); + tBarActive = false; + + } else if (val <= T_BAR_CLAMP) { + obs_transition_set_manual_time(GetCurrentTransition(), 0.0f); + tBar->blockSignals(true); + tBar->setValue(0); + tBar->blockSignals(false); + } + + tBarDown = false; +} + +void OBSBasic::TBarChanged(int value) +{ + if (!tBarDown) { + obs_transition_set_manual_torque(GetCurrentTransition(), 8.0f, + 0.05f); + TransitionToScene(GetCurrentSceneSource(), false, false, false, + 0, true); + tBarActive = true; + tBarDown = true; + } + + obs_transition_set_manual_time(GetCurrentTransition(), + (float)value / T_BAR_PRECISION_F); +} + +void OBSBasic::EnableTBar() +{ + if (!previewProgramMode) + return; + + const char *id = obs_source_get_id(GetCurrentTransition()); + + if (!id || strcmp(id, "cut_transition") == 0 || + strcmp(id, "obs_stinger_transition") == 0) { + tBar->setValue(0); + tBar->setEnabled(false); + } else { + tBar->setEnabled(true); + } +} + void OBSBasic::on_modeSwitch_clicked() { SetPreviewProgramMode(!IsPreviewProgramMode()); @@ -1137,6 +1215,8 @@ void OBSBasic::QuickTransitionChange() qt->source = GetTransitionComboItem(ui->transitions, trIdx); ResetQuickTransitionText(qt); } + + EnableTBar(); } void OBSBasic::QuickTransitionChangeDuration(int value) @@ -1258,6 +1338,7 @@ void OBSBasic::SetPreviewProgramMode(bool enabled) CreateProgramDisplay(); CreateProgramOptions(); + EnableTBar(); OBSScene curScene = GetCurrentScene(); diff --git a/UI/window-basic-main.hpp b/UI/window-basic-main.hpp index df0606cb9..5944c2a17 100644 --- a/UI/window-basic-main.hpp +++ b/UI/window-basic-main.hpp @@ -502,6 +502,11 @@ private: QIcon GetBrowserIcon() const; QIcon GetDefaultIcon() const; + QSlider *tBar; + bool tBarActive = false; + bool tBarDown = false; + void EnableTBar(); + public slots: void DeferSaveBegin(); void DeferSaveEnd(); @@ -541,7 +546,8 @@ public slots: void TransitionToScene(OBSScene scene, bool force = false); void TransitionToScene(OBSSource scene, bool force = false, bool quickTransition = false, - int quickDuration = 0, bool black = false); + int quickDuration = 0, bool black = false, + bool manual = false); void SetCurrentScene(OBSSource scene, bool force = false); bool AddSceneCollection(bool create_new, @@ -645,6 +651,9 @@ private slots: void SetSceneIcon(const QIcon &icon); void SetDefaultIcon(const QIcon &icon); + void TBarChanged(int value); + void TBarReleased(); + private: /* OBS Callbacks */ static void SceneReordered(void *data, calldata_t *params); diff --git a/libobs/obs-internal.h b/libobs/obs-internal.h index 68adce0af..7b2aa2be5 100644 --- a/libobs/obs-internal.h +++ b/libobs/obs-internal.h @@ -700,6 +700,10 @@ struct obs_source { gs_texrender_t *transition_texrender[2]; pthread_mutex_t transition_mutex; obs_source_t *transition_sources[2]; + float transition_manual_clamp; + float transition_manual_torque; + float transition_manual_target; + float transition_manual_val; bool transitioning_video; bool transitioning_audio; bool transition_source_active[2]; @@ -727,7 +731,7 @@ extern bool obs_source_init_context(struct obs_source *source, extern bool obs_transition_init(obs_source_t *transition); extern void obs_transition_free(obs_source_t *transition); -extern void obs_transition_tick(obs_source_t *transition); +extern void obs_transition_tick(obs_source_t *transition, float t); extern void obs_transition_enum_sources(obs_source_t *transition, obs_source_enum_proc_t enum_callback, void *param); diff --git a/libobs/obs-source-transition.c b/libobs/obs-source-transition.c index 375011747..d5782488c 100644 --- a/libobs/obs-source-transition.c +++ b/libobs/obs-source-transition.c @@ -16,6 +16,7 @@ ******************************************************************************/ #include "obs-internal.h" +#include "graphics/math-extra.h" #define lock_transition(transition) \ pthread_mutex_lock(&transition->transition_mutex); @@ -203,11 +204,24 @@ static void recalculate_transition_size(obs_source_t *transition) transition->transition_actual_cy = cy; } -void obs_transition_tick(obs_source_t *transition) +void obs_transition_tick(obs_source_t *transition, float t) { recalculate_transition_size(transition); recalculate_transition_matrices(transition); + if (transition->transition_mode == OBS_TRANSITION_MODE_MANUAL) { + if (transition->transition_manual_torque == 0.0f) { + transition->transition_manual_val = + transition->transition_manual_target; + } else { + transition->transition_manual_val = calc_torquef( + transition->transition_manual_val, + transition->transition_manual_target, + transition->transition_manual_torque, + transition->transition_manual_clamp, t); + } + } + if (trylock_textures(transition) == 0) { gs_texrender_reset(transition->transition_texrender[0]); gs_texrender_reset(transition->transition_texrender[1]); @@ -351,6 +365,7 @@ bool obs_transition_start(obs_source_t *transition, bool active; bool same_as_source; bool same_as_dest; + bool same_mode; if (!transition_valid(transition, "obs_transition_start")) return false; @@ -358,11 +373,21 @@ bool obs_transition_start(obs_source_t *transition, lock_transition(transition); same_as_source = dest == transition->transition_sources[0]; same_as_dest = dest == transition->transition_sources[1]; + same_mode = mode == transition->transition_mode; active = transition_active(transition); unlock_transition(transition); if (same_as_source && !active) return false; + if (active && mode == OBS_TRANSITION_MODE_MANUAL && same_mode && + same_as_dest) + return true; + + lock_transition(transition); + transition->transition_mode = mode; + transition->transition_manual_val = 0.0f; + transition->transition_manual_target = 0.0f; + unlock_transition(transition); if (transition->info.transition_start) transition->info.transition_start(transition->context.data); @@ -389,11 +414,25 @@ bool obs_transition_start(obs_source_t *transition, recalculate_transition_size(transition); recalculate_transition_matrices(transition); - /* TODO: Add mode */ - UNUSED_PARAMETER(mode); return true; } +void obs_transition_set_manual_torque(obs_source_t *transition, float torque, + float clamp) +{ + lock_transition(transition); + transition->transition_manual_torque = torque; + transition->transition_manual_clamp = clamp; + unlock_transition(transition); +} + +void obs_transition_set_manual_time(obs_source_t *transition, float t) +{ + lock_transition(transition); + transition->transition_manual_target = t; + unlock_transition(transition); +} + void obs_transition_set(obs_source_t *transition, obs_source_t *source) { obs_source_t *s[2]; @@ -429,6 +468,9 @@ void obs_transition_set(obs_source_t *transition, obs_source_t *source) static float calc_time(obs_source_t *transition, uint64_t ts) { + if (transition->transition_mode == OBS_TRANSITION_MODE_MANUAL) + return transition->transition_manual_val; + uint64_t end; if (ts <= transition->transition_start_time) diff --git a/libobs/obs-source.c b/libobs/obs-source.c index 3bee2baea..88ce0b271 100644 --- a/libobs/obs-source.c +++ b/libobs/obs-source.c @@ -1067,7 +1067,7 @@ void obs_source_video_tick(obs_source_t *source, float seconds) return; if (source->info.type == OBS_SOURCE_TYPE_TRANSITION) - obs_transition_tick(source); + obs_transition_tick(source, seconds); if ((source->info.output_flags & OBS_SOURCE_ASYNC) != 0) async_tick(source); diff --git a/libobs/obs.h b/libobs/obs.h index fe0555267..da0e1ee9f 100644 --- a/libobs/obs.h +++ b/libobs/obs.h @@ -1333,6 +1333,7 @@ EXPORT obs_source_t *obs_transition_get_active_source(obs_source_t *transition); enum obs_transition_mode { OBS_TRANSITION_MODE_AUTO, + OBS_TRANSITION_MODE_MANUAL, }; EXPORT bool obs_transition_start(obs_source_t *transition, @@ -1341,6 +1342,10 @@ EXPORT bool obs_transition_start(obs_source_t *transition, EXPORT void obs_transition_set(obs_source_t *transition, obs_source_t *source); +EXPORT void obs_transition_set_manual_time(obs_source_t *transition, float t); +EXPORT void obs_transition_set_manual_torque(obs_source_t *transition, + float torque, float clamp); + enum obs_transition_scale_type { OBS_TRANSITION_SCALE_MAX_ONLY, OBS_TRANSITION_SCALE_ASPECT,