diff --git a/rsc/images/icons.dds b/rsc/images/icons.dds
index 5f3f4d6b..4791cb3f 100644
Binary files a/rsc/images/icons.dds and b/rsc/images/icons.dds differ
diff --git a/share/metainfo/io.github.brunoherbelin.Vimix.metainfo.xml b/share/metainfo/io.github.brunoherbelin.Vimix.metainfo.xml
index f29811ab..6b730eee 100644
--- a/share/metainfo/io.github.brunoherbelin.Vimix.metainfo.xml
+++ b/share/metainfo/io.github.brunoherbelin.Vimix.metainfo.xml
@@ -29,12 +29,13 @@
vimix
-
+
- Stabilized version 0.8.2
+ Version 0.8.3
- https://github.com/brunoherbelin/vimix/releases/tag/0.8.2d
+ https://github.com/brunoherbelin/vimix/releases/tag/0.8.3
+
diff --git a/snap/snapcraft.yaml b/snap/snapcraft.yaml
index efe2d272..a60b216b 100644
--- a/snap/snapcraft.yaml
+++ b/snap/snapcraft.yaml
@@ -1,6 +1,6 @@
name: vimix
base: core22
-version: '0.8.2d'
+version: '0.8.3'
summary: Video live mixer
title: vimix
description: |
diff --git a/src/CloneSource.h b/src/CloneSource.h
index 582ed57a..14ac426b 100644
--- a/src/CloneSource.h
+++ b/src/CloneSource.h
@@ -28,6 +28,7 @@ class CloneSource : public Source
void render() override;
glm::ivec2 icon() const override;
std::string info() const override;
+ bool texturePostProcessed() const override { return true; }
// implementation of cloning mechanism
void detach();
diff --git a/src/GeometryView.cpp b/src/GeometryView.cpp
index 7e796388..0a030651 100644
--- a/src/GeometryView.cpp
+++ b/src/GeometryView.cpp
@@ -1019,8 +1019,8 @@ View::Cursor GeometryView::grab (Source *s, glm::vec2 from, glm::vec2 to, std::p
// prepare overlay frame showing full image size
glm::vec3 _c_s = glm::vec3(s->stored_status_->crop_[0]
- s->stored_status_->crop_[1],
- s->stored_status_->crop_[3]
- - s->stored_status_->crop_[2],
+ s->stored_status_->crop_[2]
+ - s->stored_status_->crop_[3],
2.f)
* 0.5f;
overlay_crop_->scale_ = s->stored_status_->scale_ / _c_s;
@@ -1028,7 +1028,7 @@ View::Cursor GeometryView::grab (Source *s, glm::vec2 from, glm::vec2 to, std::p
overlay_crop_->rotation_.z = s->stored_status_->rotation_.z;
overlay_crop_->translation_ = s->stored_status_->translation_;
glm::vec3 _t((s->stored_status_->crop_[1] + _c_s.x) * overlay_crop_->scale_.x,
- -s->stored_status_->crop_[3] + _c_s.y, 0.f);
+ (-s->stored_status_->crop_[2] + _c_s.y) * overlay_crop_->scale_.y, 0.f);
_t = glm::rotate(glm::identity(),
overlay_crop_->rotation_.z,
glm::vec3(0.f, 0.f, 1.f))
@@ -1112,8 +1112,8 @@ View::Cursor GeometryView::grab (Source *s, glm::vec2 from, glm::vec2 to, std::p
// prepare overlay frame showing full image size
glm::vec3 _c_s = glm::vec3(s->stored_status_->crop_[0]
- s->stored_status_->crop_[1],
- s->stored_status_->crop_[3]
- - s->stored_status_->crop_[2],
+ s->stored_status_->crop_[2]
+ - s->stored_status_->crop_[3],
2.f)
* 0.5f;
overlay_crop_->scale_ = s->stored_status_->scale_ / _c_s;
@@ -1121,7 +1121,7 @@ View::Cursor GeometryView::grab (Source *s, glm::vec2 from, glm::vec2 to, std::p
overlay_crop_->rotation_.z = s->stored_status_->rotation_.z;
overlay_crop_->translation_ = s->stored_status_->translation_;
glm::vec3 _t((s->stored_status_->crop_[1] + _c_s.x) * overlay_crop_->scale_.x,
- -s->stored_status_->crop_[3] + _c_s.y, 0.f);
+ (-s->stored_status_->crop_[2] + _c_s.y) * overlay_crop_->scale_.y, 0.f);
_t = glm::rotate(glm::identity(),
overlay_crop_->rotation_.z,
glm::vec3(0.f, 0.f, 1.f))
diff --git a/src/MediaPlayer.cpp b/src/MediaPlayer.cpp
index ff2e3c84..2eba6843 100644
--- a/src/MediaPlayer.cpp
+++ b/src/MediaPlayer.cpp
@@ -76,6 +76,7 @@ MediaPlayer::MediaPlayer()
video_filter_available_ = true;
position_ = GST_CLOCK_TIME_NONE;
loop_ = LoopMode::LOOP_REWIND;
+ loop_status_ = LoopStatus::LOOP_STATUS_DEFAULT;
fading_mode_ = FadingMode::FADING_COLOR;
// start index in frame_ stack
@@ -513,13 +514,13 @@ void MediaPlayer::execute_open()
if (media_.hasaudio)
Log::Info("MediaPlayer %s Audio track %s", std::to_string(id_).c_str(), audio_enabled_ ? "enabled" : "disabled");
- opened_ = true;
-
// keep name in pipeline
gst_element_set_name(pipeline_, std::to_string(id_).c_str());
// register media player
MediaPlayer::registered_.push_back(pipeline_);
+
+ opened_ = true;
}
#else
@@ -956,7 +957,7 @@ void MediaPlayer::execute_play_command(bool on)
desired_state_ = requested_state;
// if not ready yet, the requested state will be handled later
- if ( pipeline_ == nullptr )
+ if ( !opened_ || pipeline_ == nullptr )
return;
// requesting to play, but stopped at end of stream : rewind first !
@@ -980,6 +981,9 @@ void MediaPlayer::execute_play_command(bool on)
Log::Info("MediaPlayer %s Stop [%ld]", std::to_string(id_).c_str(), position());
#endif
+ // Revert loop status to default when playing
+ if (on)
+ loop_status_ = LoopStatus::LOOP_STATUS_DEFAULT;
}
void MediaPlayer::play(bool on)
@@ -992,6 +996,7 @@ void MediaPlayer::play(bool on)
if (metro_sync_ > Metronome::SYNC_NONE) {
// busy with this delayed action
pending_ = true;
+
// delayed execution function
std::function playlater = std::bind([](MediaPlayer *p, bool o) {
p->execute_play_command(o); p->pending_=false; }, this, on);
@@ -1013,7 +1018,7 @@ bool MediaPlayer::isPlaying(bool testpipeline) const
return false;
// if not ready yet, answer with requested state
- if ( !testpipeline || pipeline_ == nullptr || !enabled_)
+ if ( !testpipeline || !opened_ || pipeline_ == nullptr || !enabled_)
return desired_state_ == GST_STATE_PLAYING;
// if ready, answer with actual state
@@ -1399,14 +1404,19 @@ void MediaPlayer::execute_loop_command()
rate_ *= -1.f;
execute_seek_command();
}
- else { //LOOP_NONE
+ else {
+ if (loop_ == LOOP_BLACKOUT)
+ loop_status_ = LoopStatus::LOOP_STATUS_BLACKOUT;
+ else
+ loop_status_ = LoopStatus::LOOP_STATUS_STOPPED;
+ // stop
play(false);
}
}
void MediaPlayer::execute_seek_command(GstClockTime target, bool force)
{
- if ( pipeline_ == nullptr || !media_.seekable )
+ if ( !opened_ || pipeline_ == nullptr || !media_.seekable )
return;
// ignore request to current position
@@ -1420,15 +1430,12 @@ void MediaPlayer::execute_seek_command(GstClockTime target, bool force)
int seek_flags = GST_SEEK_FLAG_FLUSH;
// no target given
- if (target == GST_CLOCK_TIME_NONE) {
+ if (target == GST_CLOCK_TIME_NONE)
// create seek event with current position (called for rate changed)
// CLAMP the time to ensure we do not bounce outside of timeline
seek_pos = CLAMP(position_, timeline_.first(), timeline_.last() - timeline_.step());
- // seek with KEY mode if playing
- seek_flags |= GST_SEEK_FLAG_KEY_UNIT | GST_SEEK_FLAG_SNAP_AFTER;
- }
else
- // seek with accurate timing if paused
+ // seek with accurate timing if given target
seek_flags |= GST_SEEK_FLAG_ACCURATE;
// create seek event depending on direction
@@ -1538,6 +1545,9 @@ Timeline *MediaPlayer::timeline()
float MediaPlayer::currentTimelineFading()
{
+ if (loop_status_ == LOOP_STATUS_BLACKOUT && !isPlaying())
+ return 0.f;
+
return timeline_.fadingAt(position_);
}
@@ -1748,7 +1758,7 @@ void MediaPlayer::setAudioEnabled(bool on)
// toggle
audio_enabled_ = on;
// if openned
- if (media_.hasaudio ) {
+ if ( media_.hasaudio && opened_) {
// apply
reopen();
}
diff --git a/src/MediaPlayer.h b/src/MediaPlayer.h
index fa91b2b4..7cda45e0 100644
--- a/src/MediaPlayer.h
+++ b/src/MediaPlayer.h
@@ -151,7 +151,8 @@ class MediaPlayer {
typedef enum {
LOOP_NONE = 0,
LOOP_REWIND = 1,
- LOOP_BIDIRECTIONAL = 2
+ LOOP_BIDIRECTIONAL = 2,
+ LOOP_BLACKOUT = 3
} LoopMode;
/**
* Get the current loop mode
@@ -161,6 +162,12 @@ class MediaPlayer {
* Set the loop mode
* */
void setLoop(LoopMode mode);
+
+ typedef enum { LOOP_STATUS_DEFAULT = 0,
+ LOOP_STATUS_STOPPED = 1,
+ LOOP_STATUS_BLACKOUT = 2
+ } LoopStatus;
+ LoopStatus loopStatus() const { return loop_status_; }
/**
* Step when paused
* (aka next frame)
@@ -320,6 +327,7 @@ class MediaPlayer {
// GST & Play status
GstClockTime position_;
LoopMode loop_;
+ LoopStatus loop_status_;
GstState desired_state_;
GstElement *pipeline_;
GstBus *bus_;
diff --git a/src/MediaSource.cpp b/src/MediaSource.cpp
index 11c870e9..4102a9bf 100644
--- a/src/MediaSource.cpp
+++ b/src/MediaSource.cpp
@@ -49,7 +49,6 @@ void MediaSource::setPath(const std::string &p)
// open gstreamer
mediaplayer_->open(path_);
- mediaplayer_->play(true);
// will be ready after init and one frame rendered
ready_ = false;
@@ -243,3 +242,13 @@ void MediaSource::accept(Visitor& v)
Source::accept(v);
v.visit(*this);
}
+
+bool MediaSource::texturePostProcessed() const
+{
+ if (Source::texturePostProcessed())
+ return true;
+
+ return !mediaplayer_->timeline()->fadingIsClear() ||
+ mediaplayer_->loopStatus() == MediaPlayer::LOOP_STATUS_BLACKOUT;
+}
+
diff --git a/src/MediaSource.h b/src/MediaSource.h
index 70b4a906..007e59ef 100644
--- a/src/MediaSource.h
+++ b/src/MediaSource.h
@@ -25,6 +25,7 @@ class MediaSource : public Source
uint texture() const override;
void accept (Visitor& v) override;
void updateAudio() override;
+ bool texturePostProcessed () const override;
// Media specific interface
void setPath(const std::string &p);
diff --git a/src/Mixer.cpp b/src/Mixer.cpp
index 960a8c75..310ae16e 100644
--- a/src/Mixer.cpp
+++ b/src/Mixer.cpp
@@ -300,6 +300,7 @@ Source * Mixer::createSourceFile(const std::string &path, bool disable_hw_decodi
MediaSource *ms = new MediaSource;
ms->mediaplayer()->setSoftwareDecodingForced(disable_hw_decoding);
ms->setPath(path);
+ ms->play(true);
s = ms;
}
// propose a new name based on uri
diff --git a/src/SessionCreator.cpp b/src/SessionCreator.cpp
index f4fe74b7..5f80bcdc 100644
--- a/src/SessionCreator.cpp
+++ b/src/SessionCreator.cpp
@@ -1047,7 +1047,7 @@ void SessionLoader::visit (Source& s)
s.call( new Lock(l) );
bool p = true;
sourceNode->QueryBoolAttribute("play", &p);
- s.call( new Play(p) );
+ s.call( new Play(p, session_) );
xmlCurrent_ = sourceNode->FirstChildElement("Mixing");
if (xmlCurrent_) s.groupNode(View::MIXING)->accept(*this);
@@ -1150,7 +1150,7 @@ void SessionLoader::visit (MediaSource& s)
s.mediaplayer()->accept(*this);
// add a callback to activate play speed
- s.call( new PlaySpeed( s.mediaplayer()->playSpeed(), 300 ) );
+ s.call( new PlaySpeed( s.mediaplayer()->playSpeed() ) );
}
void SessionLoader::visit (SessionFileSource& s)
diff --git a/src/SessionVisitor.cpp b/src/SessionVisitor.cpp
index f76ab1c1..5040e249 100644
--- a/src/SessionVisitor.cpp
+++ b/src/SessionVisitor.cpp
@@ -436,6 +436,11 @@ void SessionVisitor::visit(MediaPlayer &n)
if (!n.singleFrame()) {
newelement->SetAttribute("loop", (int) n.loop());
+ // special case; if media player stopped by loop (not by user)
+ // then override the play status of the Source to play the video
+ if (n.loopStatus() != MediaPlayer::LOOP_STATUS_DEFAULT)
+ xmlCurrent_->SetAttribute("play", true );
+
newelement->SetAttribute("speed", n.playSpeed());
newelement->SetAttribute("video_effect", n.videoEffect().c_str());
newelement->SetAttribute("software_decoding", n.softwareDecodingForced());
diff --git a/src/Source.cpp b/src/Source.cpp
index 5e3ad090..3dea33d3 100644
--- a/src/Source.cpp
+++ b/src/Source.cpp
@@ -542,7 +542,7 @@ bool Source::textureMirrored ()
return texturesurface_->mirrorTexture();
}
-bool Source::imageProcessingEnabled()
+bool Source::imageProcessingEnabled() const
{
return ( renderingshader_ == processingshader_ );
}
@@ -733,8 +733,11 @@ bool Source::visible() const
}
-bool Source::textureTransformed () const
+bool Source::texturePostProcessed () const
{
+ if (imageProcessingEnabled())
+ return true;
+
return frame()->projectionArea() != glm::vec4(-1.f, 1.f, 1.f, -1.f) || // cropped
group(View::TEXTURE)->rotation_.z != 0.f || // rotation
group(View::TEXTURE)->scale_ != glm::vec3(1.f) || // scaled
diff --git a/src/Source.h b/src/Source.h
index 2d44023b..08a5ff0c 100644
--- a/src/Source.h
+++ b/src/Source.h
@@ -134,7 +134,7 @@ class Source : public SourceCore
// the image processing shader can be enabled or disabled
// (NB: when disabled, a simple ImageShader is applied)
void setImageProcessingEnabled (bool on);
- bool imageProcessingEnabled ();
+ bool imageProcessingEnabled () const;
// a Source has a shader to control mixing effects
inline ImageShader *blendingShader () const { return blendingshader_; }
@@ -223,7 +223,7 @@ class Source : public SourceCore
float depth () const;
float alpha () const;
bool visible() const;
- bool textureTransformed () const;
+ virtual bool texturePostProcessed () const;
// groups for mixing
MixingGroup *mixingGroup() const { return mixinggroup_; }
diff --git a/src/SourceCallback.cpp b/src/SourceCallback.cpp
index 340635dc..f3663b6f 100644
--- a/src/SourceCallback.cpp
+++ b/src/SourceCallback.cpp
@@ -157,7 +157,7 @@ void ValueSourceCallback::update(Source *s, float dt)
}
// update when active
- if ( status_ == ACTIVE ) {
+ if ( status_ == ACTIVE && s->ready() ) {
// time passed since start
float progress = elapsed_ - delay_;
@@ -494,7 +494,8 @@ void SetDepth::accept(Visitor& v)
v.visit(*this);
}
-Play::Play(bool on, bool revert) : SourceCallback(), play_(on), bidirectional_(revert)
+Play::Play(bool on, Session *parentsession, bool revert) : SourceCallback(),
+ session_(parentsession), play_(on), bidirectional_(revert)
{
}
@@ -503,7 +504,8 @@ void Play::update(Source *s, float dt)
SourceCallback::update(s, dt);
// toggle play status when ready
- if ( status_ == READY ){
+ if ( status_ == READY && s->ready() &&
+ (session_ ? session_->ready() : true) ){
if (s->playing() != play_)
// call play function
@@ -516,7 +518,7 @@ void Play::update(Source *s, float dt)
SourceCallback *Play::clone() const
{
- return new Play(play_, bidirectional_);
+ return new Play(play_, session_, bidirectional_);
}
SourceCallback *Play::reverse(Source *s) const
@@ -574,7 +576,7 @@ void PlaySpeed::writeValue(Source *s, float val)
{
// access media player if target source is a media source
MediaSource *ms = dynamic_cast(s);
- if (ms != nullptr && ms->ready()) {
+ if (ms != nullptr) {
ms->mediaplayer()->setPlaySpeed((double) val);
}
}
diff --git a/src/SourceCallback.h b/src/SourceCallback.h
index 0daac3df..5943dff6 100644
--- a/src/SourceCallback.h
+++ b/src/SourceCallback.h
@@ -212,11 +212,12 @@ class SetDepth : public SourceCallback
class Play : public SourceCallback
{
+ class Session *session_;
bool play_;
bool bidirectional_;
public:
- Play (bool on = true, bool revert = false);
+ Play (bool on = true, Session *parentsession = nullptr, bool revert = false);
bool value () const { return play_;}
void setValue (bool on) { play_ = on; }
diff --git a/src/SourceControlWindow.cpp b/src/SourceControlWindow.cpp
index 4aa9c0a8..a8568452 100644
--- a/src/SourceControlWindow.cpp
+++ b/src/SourceControlWindow.cpp
@@ -697,17 +697,6 @@ bool EditTimeline(const char *label,
hovered = true;
cursor_dot = false;
- // if a timing is given, ignore cursor timing
- if (pl->timing != GST_CLOCK_TIME_NONE) {
- // replace drop time by payload timing
- // time = pl->timing;
- // // replace mouse coordinate by position from time
- // size_t index = ((pl->timing - begin) * static_cast(MAX_TIMELINE_ARRAY)) / end ;
- // double x = static_cast(index) / static_cast(MAX_TIMELINE_ARRAY);
- // mouse_pos_in_canvas.x = ( x * (size.x - 2.f * _h_space)) + _h_space;
-
- }
-
// dragged onto the timeline : apply changes on local copy
switch (pl->action) {
case TimelinePayload::CUT:
@@ -751,7 +740,7 @@ bool EditTimeline(const char *label,
}
// dropped into the timeline : confirm change to timeline
- if (ImGui::AcceptDragDropPayload("DND_TIMELINE")) {
+ if (ImGui::AcceptDragDropPayload("DND_TIMELINE", ImGuiDragDropFlags_AcceptNoDrawDefaultRect)) {
// copy temporary timeline into given timeline
*tl = _tl;
// like a mouse release
@@ -864,7 +853,7 @@ std::list< std::pair > DrawTimeline(const char* label, Timeline
// PLOT of opacity is inside the bbox, at the top
const ImVec2 plot_pos = frame_pos + style.FramePadding;
- const ImRect plot_bbox( plot_pos, plot_pos + ImVec2(timeline_size.x, frame_size.y - 2.f * style.FramePadding.y - timeline_size.y));
+ const ImRect plot_bbox( plot_pos, plot_pos + ImVec2(timeline_size.x, frame_size.y - 4.f * style.FramePadding.y - timeline_size.y));
//
// THIRD RENDER
@@ -1363,10 +1352,7 @@ void SourceControlWindow::DrawSource(Source *s, ImVec2 framesize, ImVec2 top_ima
ImVec2 slider = framesize * ImVec2(Settings::application.widget.media_player_slider,1.f);
// draw pre and post-processed parts if necessary
- if ( (mediaplayer_active_ && !mediaplayer_active_->timeline()->fadingIsClear())||
- s->imageProcessingEnabled() ||
- s->textureTransformed() ||
- s->icon() == glm::ivec2(ICON_SOURCE_CLONE))
+ if ( s->texturePostProcessed() )
{
//
// LEFT of slider : original texture
@@ -2051,7 +2037,6 @@ void SourceControlWindow::RenderMediaPlayer(MediaSource *ms)
///
/// media player buttons bar (custom)
///
-
bottom.x = top.x;
bottom.y += 2.f * timeline_height_ + scrollbar_;
@@ -2100,8 +2085,8 @@ void SourceControlWindow::RenderMediaPlayer(MediaSource *ms)
// loop modes button
ImGui::SameLine(0, h_space_);
static int current_loop = 0;
- static std::vector< std::pair > icons_loop = { {0,15}, {1,15}, {19,14} };
- static std::vector< std::string > tooltips_loop = { "Stop at end", "Loop to start", "Bounce (reverse speed)" };
+ static std::vector< std::pair > icons_loop = { {0, 15}, {1, 15}, {19, 14}, {18, 14} };
+ static std::vector< std::string > tooltips_loop = { "Stop at end", "Loop to start", "Bounce (reverse speed)", "Stop and blackout at end" };
current_loop = (int) mediaplayer_active_->loop();
if ( ImGuiToolkit::IconMultistate(icons_loop, ¤t_loop, tooltips_loop) )
mediaplayer_active_->setLoop( (MediaPlayer::LoopMode) current_loop );
@@ -2157,16 +2142,21 @@ void SourceControlWindow::RenderMediaPlayer(MediaSource *ms)
}
else {
-
+ ///
+ /// Disabled areas for timeline and button bar
+ ///
+ // disabled timeline
ImGui::SetCursorScreenPos(bottom + ImVec2(1.f, 0.f));
-
const ImGuiContext& g = *GImGui;
const double width_ratio = static_cast(scrollwindow.x - slider_zoom_width + g.Style.FramePadding.x ) / static_cast(mediaplayer_active_->timeline()->sectionsDuration());
DrawTimeline("##timeline_mediaplayers", mediaplayer_active_->timeline(), mediaplayer_active_->position(), width_ratio, 2.f * timeline_height_);
- ///
- /// Play button bar
- ///
+ // disabled area for timeline actions and zoom
+ bottom += ImVec2(scrollwindow.x + 2.f, 0.f);
+ draw_list->AddRectFilled(bottom, bottom + ImVec2(slider_zoom_width, 2.f * timeline_height_ -1.f),
+ ImGui::GetColorU32(ImGuiCol_FrameBgActive));
+ // disabled button bar
+ bottom.x = top.x;
bottom.y += 2.f * timeline_height_ + scrollbar_;
DrawButtonBar(bottom, rendersize.x);
}
@@ -2209,7 +2199,7 @@ void SourceControlWindow::RenderMediaPlayer(MediaSource *ms)
///
/// Window area to edit gaps or fading
///
- if (mediaplayer_edit_panel_) {
+ if (mediaplayer_edit_panel_ && mediaplayer_active_->isEnabled()) {
const ImVec2 gap_dialog_size(buttons_width_ * 3.0f, buttons_height_ * 1.42);
const ImVec2 gap_dialog_pos = rendersize + ImVec2(h_space_, buttons_height_) - gap_dialog_size;
@@ -2274,75 +2264,45 @@ void SourceControlWindow::RenderMediaPlayer(MediaSource *ms)
}
///
- /// SEPARATOR
+ /// SECTION WITH BUTTONS
///
ImGui::SameLine(0, 0);
ImGui::Text("|");
- ///
- /// Enter time or percentage of time
- ///
+ /// Enter time value for CUT
target_time = MIN(target_time, tl->duration());
-
ImGui::SameLine(0, 0);
ImVec2 draw_pos = ImGui::GetCursorPos();
- ImGui::SetCursorPosY(ImGui::GetTextLineHeightWithSpacing() * 0.333 );
-
float w = gap_dialog_size.x - 4.f * ImGui::GetTextLineHeightWithSpacing() ;
+ ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(10, 11));
ImGui::SetNextItemWidth(w - draw_pos.x - IMGUI_SAME_LINE); // VARIABLE WIDTH
ImGuiToolkit::InputTime("##Time", &target_time);
+ ImGui::PopStyleVar();
- // ImGui::SetCursorPos(ImVec2(draw_pos.x, draw_pos.y + 35));
- // ImGuiToolkit::HSliderUInt64("#SliderTime", ImVec2(180, 14), &target_time, 0, tl->duration());
-
- // static int p = 5;
- // ImGuiToolkit::HSliderInt("#toto", ImVec2(140, 14), &p, 1, 9);
- // if (ImGui::IsItemActive()) {
- // ImGuiToolkit::PushFont(ImGuiToolkit::FONT_DEFAULT);
- // ImGui::SetTooltip("%d%%", p * 10);
- // ImGui::PopFont();
- // }
- // if (ImGui::IsItemDeactivatedAfterEdit()){
-
- // }
-
- // ImGui::SameLine(0, IMGUI_SAME_LINE);
- // ImVec2 draw_pos = ImGui::GetCursorPos() + ImVec2(0, v_space_);
- // ImGui::SetCursorPos(ImVec2(gap_dialog_size.x - ImGui::GetTextLineHeightWithSpacing()- IMGUI_SAME_LINE,
- // draw_pos.y));
- // static bool percentage = false;
- // ImGuiToolkit::IconToggle(19, 10, 3, 7, &percentage);
- // ImGui::SetCursorPos(draw_pos);
- // if (percentage) {
- // int target_percent = (int) ( (100 * target_time) / tl->duration() );
- // ImGui::SetNextItemWidth(-ImGui::GetTextLineHeightWithSpacing());
- // ImGui::DragInt("##percent", &target_percent, 1, 0, 100);
- // target_time = (tl->duration() * (guint64) target_percent) / 100;
- // }
- // else {
- // ImGui::SetNextItemWidth(-ImGui::GetTextLineHeightWithSpacing());
- // ImGuiToolkit::InputTime("##Time", &target_time);
- // }
-
- // Action buttons
+ /// CUT LEFT TIME
ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0, 0, 0, 0));
ImGui::SetCursorPos( ImVec2(w, draw_pos.y));
-
- // ImGui::SameLine(0, IMGUI_SAME_LINE);
if (ImGuiToolkit::ButtonIcon(17, 3, "Cut left at given time")) {
tl->cut(target_time, true);
tl->refresh();
+ oss << ": Timeline cut";
+ Action::manager().store(oss.str());
}
-
+ /// CUT RIGHT TIME
ImGui::SameLine(0, IMGUI_SAME_LINE);
if (ImGuiToolkit::ButtonIcon(18, 3, "Cut right at given time")){
tl->cut(target_time, false);
tl->refresh();
+ oss << ": Timeline cut";
+ Action::manager().store(oss.str());
}
-
+ /// CLEAR
ImGui::SameLine(0, IMGUI_SAME_LINE);
- if (ImGuiToolkit::ButtonIcon(11, 14, "Clear all gaps"))
+ if (ImGuiToolkit::ButtonIcon(11, 14, "Clear all gaps")) {
tl->clearGaps();
+ oss << ": Timeline clear gaps";
+ Action::manager().store(oss.str());
+ }
ImGui::PopStyleColor();
@@ -2368,9 +2328,7 @@ void SourceControlWindow::RenderMediaPlayer(MediaSource *ms)
{14, 12}};
ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0, 0, 0, 0));
- // ImGuiToolkit::ButtonIconMultistate(options_curve, ¤t_curve, tooltips_curve);
ImGuiToolkit::IconMultistate(options_curve, ¤t_curve, tooltips_curve);
-
ImGui::PopStyleColor();
///
@@ -2395,12 +2353,6 @@ void SourceControlWindow::RenderMediaPlayer(MediaSource *ms)
ImGui::SameLine(0, IMGUI_SAME_LINE);
DragButtonIcon(9, 10, "Drop in timeline to insert\nSharp fade in & out",
TimelinePayload(TimelinePayload::FADE_IN_OUT, d*GST_MSECOND, Timeline::FADING_SHARP));
- ///
- /// FADE OUT & IN
- ///
- ImGui::SameLine(0, IMGUI_SAME_LINE);
- DragButtonIcon(10, 10, "Drop in timeline to insert\nSharp fade out & in",
- TimelinePayload(TimelinePayload::FADE_OUT_IN, d*GST_MSECOND, Timeline::FADING_SHARP));
}
///
/// FADE LINEAR
@@ -2424,12 +2376,6 @@ void SourceControlWindow::RenderMediaPlayer(MediaSource *ms)
ImGui::SameLine(0, IMGUI_SAME_LINE);
DragButtonIcon(7, 10, "Drop in timeline to insert\nLinear fade in & out",
TimelinePayload(TimelinePayload::FADE_IN_OUT, d*GST_MSECOND, Timeline::FADING_LINEAR));
- ///
- /// FADE OUT & IN
- ///
- ImGui::SameLine(0, IMGUI_SAME_LINE);
- DragButtonIcon(8, 10, "Drop in timeline to insert\nLinear fade out & in",
- TimelinePayload(TimelinePayload::FADE_OUT_IN, d*GST_MSECOND, Timeline::FADING_LINEAR));
}
///
/// FADE SMOOTH
@@ -2453,89 +2399,70 @@ void SourceControlWindow::RenderMediaPlayer(MediaSource *ms)
ImGui::SameLine(0, IMGUI_SAME_LINE);
DragButtonIcon(17, 10, "Drop in timeline to insert\nSmooth fade in & out",
TimelinePayload(TimelinePayload::FADE_IN_OUT, d*GST_MSECOND, Timeline::FADING_SMOOTH));
- ///
- /// FADE OUT & IN
- ///
- ImGui::SameLine(0, IMGUI_SAME_LINE);
- DragButtonIcon(18, 10, "Drop in timeline to insert\nSmooth fade out & in",
- TimelinePayload(TimelinePayload::FADE_OUT_IN, d*GST_MSECOND, Timeline::FADING_SMOOTH));
-
}
- // float md = 10000;
- ImGui::SameLine(0, IMGUI_SAME_LINE);
+ ///
+ /// DURATION SLIDER
+ ///
+ float seconds = (float) d / 1000.f;
+ float maxi = (float) GST_TIME_AS_MSECONDS(tl->duration()) / 1000.f;
+ float w = gap_dialog_size.x - 4.f * ImGui::GetTextLineHeightWithSpacing();
+ ImGui::SameLine(0, IMGUI_SAME_LINE);
ImVec2 draw_pos = ImGui::GetCursorPos();
- float w = gap_dialog_size.x - 3.f * ImGui::GetTextLineHeightWithSpacing() - IMGUI_SAME_LINE;
+ ImGuiToolkit::PushFont(ImGuiToolkit::FONT_MONO);
+ ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(10, 11));
ImGui::SetNextItemWidth(w - draw_pos.x);
- float seconds = (float) d / 1000.f;
-
- if (current_curve > 0) {
- // ImGuiToolkit::SliderTiming("##Duration", &d, 60, md+50, 50, "Auto");
- // if (d > md) d = UINT_MAX;
- if (ImGui::SliderFloat("##DurationFading",
- &seconds,
- 0.05f,
- 60.f,
- seconds < 59.5f ? "%.2f s" : "Auto")) {
- if (seconds > 59.5f)
- d = UINT_MAX;
- else
- d = (uint) floor(seconds * 1000);
- }
- } else {
- // ImGui::TextDisabled("%.2f s", seconds);
- static char dummy_str[512];
- if (seconds < 59.5f )
- snprintf(dummy_str, 512, "%.2f s", seconds);
+ if (ImGui::SliderFloat("##DurationFading",
+ &seconds,
+ 0.05f,
+ maxi,
+ d < UINT_MAX ? "%.2f s" : "Auto")) {
+ if (seconds > maxi - 0.05f)
+ d = UINT_MAX;
else
- snprintf(dummy_str, 512, "Auto");
-
- ImGui::PushStyleColor(ImGuiCol_FrameBg, ImVec4(0.14f, 0.14f, 0.14f, 0.9f));
- ImGui::InputText("##disabledduration", dummy_str, IM_ARRAYSIZE(dummy_str), ImGuiInputTextFlags_ReadOnly);
- ImGui::PopStyleColor(1);
-
+ d = (uint) floor(seconds * 1000);
}
-
-
- // Action buttons
- ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0, 0, 0, 0));
+ ImGui::PopStyleVar();
+ ImGui::PopFont();
///
- /// SEPARATOR
+ /// SECTION WITH BUTTONS
///
- ImGui::SameLine(0, 0);
+ ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0, 0, 0, 0));
+ ImGui::SetCursorPos( ImVec2(w, draw_pos.y));
ImGui::Text("|");
- // action smooth
+ /// SMOOTH Curve
static int _actionsmooth = 0;
ImGui::SameLine(0, 0);
ImGui::PushButtonRepeat(true);
if (ImGuiToolkit::ButtonIcon(2, 7, "Apply smoothing filter")){
- tl->smoothFading( 5 );
+ tl->smoothFading( 15 );
++_actionsmooth;
}
ImGui::PopButtonRepeat();
if (_actionsmooth > 0 && ImGui::IsMouseReleased(ImGuiMouseButton_Left)) {
- oss << ": Timeline opacity smooth";
+ oss << ": Timeline smooth curve";
Action::manager().store(oss.str());
_actionsmooth = 0;
}
+ /// CLEANUP
+ ImGui::SameLine(0, 0);
+ if (ImGuiToolkit::ButtonIcon(3, 7, "Clean curve in gaps")) {
+ tl->autoFadeInGaps();
+ oss << ": Timeline cleanup curve";
+ Action::manager().store(oss.str());
+ }
- // ImGui::SameLine(0, IMGUI_SAME_LINE);
- // ImGui::SetNextItemWidth(140); // TODO VARIABLE WIDTH
- // ImVec2 draw_pos = ImGui::GetCursorPos();
- // ImGui::SetCursorPosY(draw_pos.y + 8); // TODO VARIABLE H
- // ImGuiToolkit::InputTime("##Time", &target_time);
-
-
- ///
/// CLEAR
- ///
- ImGui::SameLine(0, IMGUI_SAME_LINE);
- if (ImGuiToolkit::ButtonIcon(11, 14, "Clear Fading curve"))
+ ImGui::SameLine(0, 0);
+ if (ImGuiToolkit::ButtonIcon(11, 14, "Clear fading curve")){
tl->clearFading();
+ oss << ": Timeline clear fading";
+ Action::manager().store(oss.str());
+ }
ImGui::PopStyleColor();
diff --git a/src/Timeline.cpp b/src/Timeline.cpp
index 7d5b7a2e..f2c91d62 100644
--- a/src/Timeline.cpp
+++ b/src/Timeline.cpp
@@ -40,6 +40,10 @@ float linestep(size_t edge0, size_t edge1, size_t i) {
return x;
}
+float sharpstep(size_t edge0, size_t edge1, size_t i) {
+ float x = clamp( static_cast(i - edge0) / static_cast(edge1 - edge0));
+ return fabs(x)<1.f ? 0.f : 1.f;
+}
@@ -545,7 +549,6 @@ void Timeline::clearFading()
fading_array_allones_ = true;
}
-
bool Timeline::fadingIsClear()
{
if (fading_array_changed_) {
@@ -696,18 +699,16 @@ void Timeline::fadeOut(const GstClockTime from, const GstClockTime duration, Fad
N = 2;
}
- // linear fade out starts at s
+ // fade out starts at s
size_t i = s;
// float val = fadingArray_[s];
for (; i <= s+n; ++i) {
- const float x = static_cast(e-i) / static_cast(n);
- // const float x = 1.f - static_cast(i-s) / static_cast(n);
if (curve==FADING_LINEAR)
- fadingArray_[i] = x;
+ fadingArray_[i] = 1.f - linestep(s, s+n, i);
else if (curve==FADING_SMOOTH)
fadingArray_[i] = 1.f - smootherstep(s, s+n, i);
else
- fadingArray_[i] = 0.f;
+ fadingArray_[i] = sharpstep(s, s+n, i);
// fadingArray_[i] *= val;
}
fading_array_changed_ = true;
@@ -746,18 +747,16 @@ void Timeline::fadeIn(const GstClockTime to, const GstClockTime duration, Fading
// calculate size of the smooth transition in [s e] interval
const size_t n = MIN( e-s, N );
- // linear fade in ends at e
+ // fade in ends at e
size_t i = e-n;
// float val = fadingArray_[e];
for (; i < e; ++i) {
- // const float x = static_cast(i-s) / static_cast(n);
- const float x = 1.f - static_cast(e - i) / static_cast(n);
if (curve==FADING_LINEAR)
- fadingArray_[i] = x;
+ fadingArray_[i] = linestep(e-n, e, i);
else if (curve==FADING_SMOOTH)
fadingArray_[i] = smootherstep(e-n, e, i);
else
- fadingArray_[i] = 0.f;
+ fadingArray_[i] = sharpstep(e-n, e, i);
// fadingArray_[i] *= val;
}
fading_array_changed_ = true;
@@ -771,12 +770,7 @@ void Timeline::fadeInOutRange(const GstClockTime t, const GstClockTime duration,
// find cuts at left and right of given time
for (auto g = gaps_.begin(); g != gaps_.end(); g++) {
if (g->begin < t) {
- if (g->end > t) {
- // inside a gap
- range.begin = g->begin;
- range.end = g->end;
- break;
- } else {
+ if (g->end < t) {
// after a gap
range.begin = g->end;
}
@@ -794,48 +788,56 @@ void Timeline::fadeInOutRange(const GstClockTime t, const GstClockTime duration,
const size_t e = (range.end * MAX_TIMELINE_ARRAY) / timing_.end;
// get index of time in section
- const size_t m = (t * MAX_TIMELINE_ARRAY) / timing_.end;
-
- size_t l = m;
- size_t r = m;
+ size_t l = ( MIN(t, range.begin + duration) * MAX_TIMELINE_ARRAY) / timing_.end;
+ size_t r = ( MAX(t, range.end - duration) * MAX_TIMELINE_ARRAY) / timing_.end;
// if duration too short for a linear or smooth
if (duration < 2 * step_) {
curve = FADING_SHARP;
}
- // if duration allows to fade in and out
- else if (2 * duration < range.duration()) {
- l = ( (range.begin + duration) * MAX_TIMELINE_ARRAY) / timing_.end;
- r = ( (range.end - duration) * MAX_TIMELINE_ARRAY) / timing_.end;
+ else if (duration > range.duration()) {
+ if (curve == FADING_SHARP) {
+ l = s+1;
+ r = e-1;
+ }
+ else
+ l = r = (t * MAX_TIMELINE_ARRAY) / timing_.end;
}
// fill values inside range
- for (size_t k = s; k < e; ++k) {
+ for (size_t k = s; k <= e; ++k) {
if (curve == FADING_LINEAR){
if (k 0 ? fadingArray_[i-1] : 0;
+ }
+ }
+}
+
void Timeline::updateGapsFromArray(float *array, size_t array_size)
{
// reset gaps
diff --git a/src/Timeline.h b/src/Timeline.h
index 9383f4f4..39cf96e8 100644
--- a/src/Timeline.h
+++ b/src/Timeline.h
@@ -165,7 +165,10 @@ class Timeline
void fadeIn(const GstClockTime from, const GstClockTime duration, FadingCurve curve = FADING_SHARP);
void fadeOut(const GstClockTime to, const GstClockTime duration, FadingCurve curve = FADING_SHARP);
void fadeInOutRange(const GstClockTime t, const GstClockTime duration, bool in_and_out, FadingCurve curve = FADING_SHARP);
- bool autoCut();
+
+ // link Fading and Gaps
+ bool autoGapInFade();
+ void autoFadeInGaps();
private:
diff --git a/src/TransitionView.cpp b/src/TransitionView.cpp
index 4c5ebfa2..94acf7f6 100644
--- a/src/TransitionView.cpp
+++ b/src/TransitionView.cpp
@@ -234,7 +234,7 @@ void TransitionView::draw()
// toggle transition mode
ImGui::SetCursorScreenPos(ImVec2(pos_tran.x - 60.f, pos_tran.y +2.f));
const char *tooltip[2] = {"Fade to black", "Cross fading"};
- ImGuiToolkit::IconToggle(0, 2, 0, 8, &Settings::application.transition.cross_fade, tooltip );
+ ImGuiToolkit::IconToggle(9, 8, 0, 8, &Settings::application.transition.cross_fade, tooltip );
ImGui::SetCursorScreenPos(ImVec2(pos_tran.x + 10.f, pos_tran.y + 2.f));
const char *_tooltip[2] = {"Linear", "Quadratic"};
diff --git a/src/UserInterfaceManager.cpp b/src/UserInterfaceManager.cpp
index 40fd25fd..222c430f 100644
--- a/src/UserInterfaceManager.cpp
+++ b/src/UserInterfaceManager.cpp
@@ -6021,7 +6021,7 @@ void Navigator::RenderTransitionPannel(const ImVec2 &iconsize)
static std::vector< std::tuple > profile_fading = {
{0, 8, "Cross fading"},
- {0, 2, "Fade to black"}
+ {9, 8, "Fade to black"}
};
ImGui::SetNextItemWidth(IMGUI_RIGHT_ALIGN);
int tmp = Settings::application.transition.cross_fade ? 0 : 1;