mirror of
https://github.com/brunoherbelin/vimix.git
synced 2025-12-13 03:09:57 +01:00
First operational implementation of video recorder.
This commit is contained in:
143
Recorder.cpp
143
Recorder.cpp
@@ -11,6 +11,8 @@
|
||||
#include <gst/gstformat.h>
|
||||
#include <gst/video/video.h>
|
||||
|
||||
#include "Settings.h"
|
||||
#include "GstToolkit.h"
|
||||
#include "defines.h"
|
||||
#include "SystemToolkit.h"
|
||||
#include "FrameBuffer.h"
|
||||
@@ -20,17 +22,19 @@
|
||||
|
||||
using namespace std;
|
||||
|
||||
Recorder::Recorder() : enabled_(true), finished_(false)
|
||||
Recorder::Recorder() : finished_(false)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
|
||||
PNGRecorder::PNGRecorder() : Recorder()
|
||||
{
|
||||
filename_ = SystemToolkit::home_path() + SystemToolkit::date_time_string() + "_vimix.png";
|
||||
}
|
||||
std::string path = SystemToolkit::path_directory(Settings::application.record.path);
|
||||
if (path.empty())
|
||||
path = SystemToolkit::home_path();
|
||||
|
||||
filename_ = path + SystemToolkit::date_time_string() + "_vimix.png";
|
||||
}
|
||||
|
||||
// Thread to perform slow operation of saving to file
|
||||
void save_png(std::string filename, unsigned char *data, uint w, uint h, uint c)
|
||||
@@ -48,40 +52,68 @@ void save_png(std::string filename, unsigned char *data, uint w, uint h, uint c)
|
||||
|
||||
void PNGRecorder::addFrame(FrameBuffer *frame_buffer, float)
|
||||
{
|
||||
if (enabled_)
|
||||
{
|
||||
uint w = frame_buffer->width();
|
||||
uint h = frame_buffer->height();
|
||||
uint c = frame_buffer->use_alpha() ? 4 : 3;
|
||||
GLenum format = frame_buffer->use_alpha() ? GL_RGBA : GL_RGB;
|
||||
uint size = w * h * c;
|
||||
unsigned char * data = (unsigned char*) malloc(size);
|
||||
|
||||
glGetTextureSubImage( frame_buffer->texture(), 0, 0, 0, 0, w, h, 1, format, GL_UNSIGNED_BYTE, size, data);
|
||||
uint w = frame_buffer->width();
|
||||
uint h = frame_buffer->height();
|
||||
uint c = frame_buffer->use_alpha() ? 4 : 3;
|
||||
GLenum format = frame_buffer->use_alpha() ? GL_RGBA : GL_RGB;
|
||||
uint size = w * h * c;
|
||||
unsigned char * data = (unsigned char*) malloc(size);
|
||||
|
||||
// save in separate thread
|
||||
std::thread(save_png, filename_, data, w, h, c).detach();
|
||||
}
|
||||
glGetTextureSubImage( frame_buffer->texture(), 0, 0, 0, 0, w, h, 1, format, GL_UNSIGNED_BYTE, size, data);
|
||||
|
||||
// save in separate thread
|
||||
std::thread(save_png, filename_, data, w, h, c).detach();
|
||||
|
||||
// record one frame only
|
||||
finished_ = true;
|
||||
}
|
||||
|
||||
const char* VideoRecorder::profile_name[4] = { "H264 (high)", "H264 (low)", "Apple ProRes 4444", "WebM VP9" };
|
||||
const std::vector<std::string> VideoRecorder::profile_description {
|
||||
// Control x264 encoder quality :
|
||||
// pass=4
|
||||
// quant (4) – Constant Quantizer
|
||||
// qual (5) – Constant Quality
|
||||
// quantizer=23
|
||||
// The total range is from 0 to 51, where 0 is lossless, 18 can be considered ‘visually lossless’,
|
||||
// and 51 is terrible quality. A sane range is 18-26, and the default is 23.
|
||||
// speed-preset=3
|
||||
// veryfast (3)
|
||||
// faster (4)
|
||||
// fast (5)
|
||||
"x264enc pass=4 quantizer=20 speed-preset=3 ! video/x-h264, profile=high ! h264parse ! ",
|
||||
"x264enc pass=4 quantizer=23 speed-preset=3 ! video/x-h264, profile=baseline ! h264parse ! ",
|
||||
// Apple ProRes encoding parameters
|
||||
// pass=2
|
||||
// cbr (0) – Constant Bitrate Encoding
|
||||
// quant (2) – Constant Quantizer
|
||||
// pass1 (512) – VBR Encoding - Pass 1
|
||||
"avenc_prores bitrate=60000 pass=2 ! ",
|
||||
// WebM VP9 encoding parameters
|
||||
// https://www.webmproject.org/docs/encoder-parameters/
|
||||
// https://developers.google.com/media/vp9/settings/vod/
|
||||
"vp9enc end-usage=vbr end-usage=vbr cpu-used=3 max-quantizer=35 target-bitrate=200000 keyframe-max-dist=360 token-partitions=2 static-threshold=1000 ! "
|
||||
|
||||
};
|
||||
|
||||
|
||||
H264Recorder::H264Recorder() : Recorder(), frame_buffer_(nullptr), width_(0), height_(0), buf_size_(0),
|
||||
VideoRecorder::VideoRecorder() : Recorder(), frame_buffer_(nullptr), width_(0), height_(0), buf_size_(0),
|
||||
recording_(false), pipeline_(nullptr), src_(nullptr), timestamp_(0), time_(0), accept_buffer_(false)
|
||||
{
|
||||
// auto filename
|
||||
filename_ = SystemToolkit::home_path() + SystemToolkit::date_time_string() + "_vimix.mov";
|
||||
std::string path = SystemToolkit::path_directory(Settings::application.record.path);
|
||||
if (path.empty())
|
||||
path = SystemToolkit::home_path();
|
||||
|
||||
// configure H264stream
|
||||
filename_ = path + SystemToolkit::date_time_string() + "_vimix.mov";
|
||||
|
||||
// configure fix parameter
|
||||
frame_duration_ = gst_util_uint64_scale_int (1, GST_SECOND, 30); // 30 FPS
|
||||
time_ = 2 * frame_duration_;
|
||||
|
||||
}
|
||||
|
||||
H264Recorder::~H264Recorder()
|
||||
VideoRecorder::~VideoRecorder()
|
||||
{
|
||||
if (pipeline_ != nullptr) {
|
||||
gst_element_set_state (pipeline_, GST_STATE_NULL);
|
||||
@@ -89,11 +121,9 @@ H264Recorder::~H264Recorder()
|
||||
}
|
||||
if (src_ != nullptr)
|
||||
gst_object_unref (src_);
|
||||
|
||||
}
|
||||
|
||||
|
||||
void H264Recorder::addFrame (FrameBuffer *frame_buffer, float dt)
|
||||
void VideoRecorder::addFrame (FrameBuffer *frame_buffer, float dt)
|
||||
{
|
||||
// TODO : avoid software videoconvert by using a GPU shader to produce Y444 frames
|
||||
|
||||
@@ -113,6 +143,9 @@ void H264Recorder::addFrame (FrameBuffer *frame_buffer, float dt)
|
||||
buf_size_ = width_ * height_ * (frame_buffer_->use_alpha() ? 4 : 3);
|
||||
|
||||
// create a gstreamer pipeline
|
||||
string description = "appsrc name=src ! videoconvert ! ";
|
||||
description += profile_description[Settings::application.record.profile];
|
||||
description += "qtmux ! filesink name=sink";
|
||||
|
||||
// Control x264 encoder quality :
|
||||
// pass=4
|
||||
@@ -125,9 +158,9 @@ void H264Recorder::addFrame (FrameBuffer *frame_buffer, float dt)
|
||||
// veryfast (3)
|
||||
// faster (4)
|
||||
// fast (5)
|
||||
string description = "appsrc name=src ! videoconvert ! "
|
||||
"x264enc pass=4 quantizer=23 speed-preset=3 ! video/x-h264, profile=high ! h264parse ! "
|
||||
"qtmux ! filesink name=sink";
|
||||
// string description = "appsrc name=src ! videoconvert ! "
|
||||
// "x264enc pass=4 quantizer=23 speed-preset=3 ! video/x-h264, profile=high ! h264parse ! "
|
||||
// "qtmux ! filesink name=sink";
|
||||
|
||||
// WebM VP9 encoding parameters
|
||||
// https://www.webmproject.org/docs/encoder-parameters/
|
||||
@@ -157,7 +190,7 @@ void H264Recorder::addFrame (FrameBuffer *frame_buffer, float dt)
|
||||
GError *error = NULL;
|
||||
pipeline_ = gst_parse_launch (description.c_str(), &error);
|
||||
if (error != NULL) {
|
||||
Log::Warning("H264Recorder Could not construct pipeline %s:\n%s", description.c_str(), error->message);
|
||||
Log::Warning("VideoRecorder Could not construct pipeline %s:\n%s", description.c_str(), error->message);
|
||||
g_clear_error (&error);
|
||||
finished_ = true;
|
||||
return;
|
||||
@@ -172,7 +205,7 @@ void H264Recorder::addFrame (FrameBuffer *frame_buffer, float dt)
|
||||
NULL);
|
||||
}
|
||||
else {
|
||||
Log::Warning("H264Recorder Could not configure file");
|
||||
Log::Warning("VideoRecorder Could not configure file");
|
||||
finished_ = true;
|
||||
return;
|
||||
}
|
||||
@@ -211,7 +244,7 @@ void H264Recorder::addFrame (FrameBuffer *frame_buffer, float dt)
|
||||
|
||||
}
|
||||
else {
|
||||
Log::Warning("H264Recorder Could not configure capture source");
|
||||
Log::Warning("VideoRecorder Could not configure capture source");
|
||||
finished_ = true;
|
||||
return;
|
||||
}
|
||||
@@ -219,13 +252,13 @@ void H264Recorder::addFrame (FrameBuffer *frame_buffer, float dt)
|
||||
// start recording
|
||||
GstStateChangeReturn ret = gst_element_set_state (pipeline_, GST_STATE_PLAYING);
|
||||
if (ret == GST_STATE_CHANGE_FAILURE) {
|
||||
Log::Warning("H264Recorder Could not record %s", filename_.c_str());
|
||||
Log::Warning("VideoRecorder Could not record %s", filename_.c_str());
|
||||
finished_ = true;
|
||||
return;
|
||||
}
|
||||
|
||||
// all good
|
||||
Log::Info("H264Recorder start recording (%d x %d)", width_, height_);
|
||||
Log::Info("VideoRecorder start recording (%d x %d)", width_, height_);
|
||||
|
||||
// start recording !!
|
||||
recording_ = true;
|
||||
@@ -270,19 +303,16 @@ void H264Recorder::addFrame (FrameBuffer *frame_buffer, float dt)
|
||||
gst_buffer_unmap (buffer, &map);
|
||||
|
||||
// push
|
||||
// Log::Info("H264Recorder push data %ld", buffer->pts);
|
||||
// Log::Info("VideoRecorder push data %ld", buffer->pts);
|
||||
gst_app_src_push_buffer (src_, buffer);
|
||||
// NB: buffer will be unrefed by the appsrc
|
||||
|
||||
// TODO : detect user request for end
|
||||
count++;
|
||||
if (count > 120)
|
||||
{
|
||||
// Log::Info("H264Recorder push EOS");
|
||||
gst_app_src_end_of_stream (src_);
|
||||
|
||||
recording_ = false;
|
||||
}
|
||||
// // TODO : detect user request for end
|
||||
// count++;
|
||||
// if (count > 120)
|
||||
// {
|
||||
// stop();
|
||||
// }
|
||||
|
||||
// restart counter
|
||||
time_ = 0;
|
||||
@@ -305,34 +335,49 @@ void H264Recorder::addFrame (FrameBuffer *frame_buffer, float dt)
|
||||
// stop the pipeline
|
||||
GstStateChangeReturn ret = gst_element_set_state (pipeline_, GST_STATE_NULL);
|
||||
if (ret == GST_STATE_CHANGE_FAILURE)
|
||||
Log::Warning("H264Recorder Could not stop");
|
||||
Log::Warning("VideoRecorder Could not stop");
|
||||
else
|
||||
Log::Notify("H264Recording finished");
|
||||
Log::Notify("Recording %s ready.", filename_.c_str());
|
||||
|
||||
count = 0;
|
||||
finished_ = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void VideoRecorder::stop ()
|
||||
{
|
||||
// send end of stream
|
||||
gst_app_src_end_of_stream (src_);
|
||||
// Log::Info("VideoRecorder push EOS");
|
||||
|
||||
// stop recording
|
||||
recording_ = false;
|
||||
}
|
||||
|
||||
std::string VideoRecorder::info()
|
||||
{
|
||||
if (recording_)
|
||||
return GstToolkit::time_to_string(timestamp_);
|
||||
else
|
||||
return "Saving file...";
|
||||
}
|
||||
|
||||
// appsrc needs data and we should start sending
|
||||
void H264Recorder::callback_need_data (GstAppSrc *src, guint length, gpointer p)
|
||||
void VideoRecorder::callback_need_data (GstAppSrc *src, guint length, gpointer p)
|
||||
{
|
||||
// Log::Info("H264Recording callback_need_data");
|
||||
H264Recorder *rec = (H264Recorder *)p;
|
||||
VideoRecorder *rec = (VideoRecorder *)p;
|
||||
if (rec) {
|
||||
rec->accept_buffer_ = rec->recording_ ? true : false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// appsrc has enough data and we can stop sending
|
||||
void H264Recorder::callback_enough_data (GstAppSrc *src, gpointer p)
|
||||
void VideoRecorder::callback_enough_data (GstAppSrc *src, gpointer p)
|
||||
{
|
||||
// Log::Info("H264Recording callback_enough_data");
|
||||
H264Recorder *rec = (H264Recorder *)p;
|
||||
VideoRecorder *rec = (VideoRecorder *)p;
|
||||
if (rec) {
|
||||
rec->accept_buffer_ = false;
|
||||
}
|
||||
|
||||
20
Recorder.h
20
Recorder.h
@@ -3,6 +3,7 @@
|
||||
|
||||
#include <atomic>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include <gst/pbutils/pbutils.h>
|
||||
#include <gst/app/gstappsrc.h>
|
||||
@@ -22,15 +23,12 @@ public:
|
||||
virtual ~Recorder() {}
|
||||
|
||||
virtual void addFrame(FrameBuffer *frame_buffer, float dt) = 0;
|
||||
virtual void stop() { }
|
||||
virtual std::string info() { return ""; }
|
||||
|
||||
inline void finish() { finished_ = true; }
|
||||
inline bool finished() const { return finished_; }
|
||||
|
||||
inline void setEnabled( bool on ) { enabled_ = on; }
|
||||
inline bool enabled() const { return enabled_; }
|
||||
|
||||
protected:
|
||||
std::atomic<bool> enabled_;
|
||||
std::atomic<bool> finished_;
|
||||
};
|
||||
|
||||
@@ -46,7 +44,7 @@ public:
|
||||
};
|
||||
|
||||
|
||||
class H264Recorder : public Recorder
|
||||
class VideoRecorder : public Recorder
|
||||
{
|
||||
std::string filename_;
|
||||
|
||||
@@ -73,11 +71,15 @@ class H264Recorder : public Recorder
|
||||
|
||||
public:
|
||||
|
||||
H264Recorder();
|
||||
~H264Recorder();
|
||||
static const char* profile_name[4];
|
||||
static const std::vector<std::string> profile_description;
|
||||
|
||||
VideoRecorder();
|
||||
~VideoRecorder();
|
||||
|
||||
void addFrame(FrameBuffer *frame_buffer, float dt);
|
||||
|
||||
void stop() override;
|
||||
std::string info() override;
|
||||
|
||||
};
|
||||
|
||||
|
||||
12
Session.cpp
12
Session.cpp
@@ -79,8 +79,7 @@ void Session::update(float dt)
|
||||
{
|
||||
Recorder *rec = *iter;
|
||||
|
||||
if (rec->enabled())
|
||||
rec->addFrame(render_.frame(), dt);
|
||||
rec->addFrame(render_.frame(), dt);
|
||||
|
||||
if (rec->finished()) {
|
||||
iter = recorders_.erase(iter);
|
||||
@@ -233,6 +232,15 @@ void Session::addRecorder(Recorder *rec)
|
||||
recorders_.push_back(rec);
|
||||
}
|
||||
|
||||
|
||||
Recorder *Session::frontRecorder()
|
||||
{
|
||||
if (recorders_.empty())
|
||||
return nullptr;
|
||||
else
|
||||
return recorders_.front();
|
||||
}
|
||||
|
||||
void Session::clearRecorders()
|
||||
{
|
||||
std::list<Recorder *>::iterator iter;
|
||||
|
||||
@@ -49,6 +49,7 @@ public:
|
||||
|
||||
// add recorders
|
||||
void addRecorder(Recorder *rec);
|
||||
Recorder *frontRecorder();
|
||||
void clearRecorders();
|
||||
|
||||
// configure rendering resolution
|
||||
|
||||
19
Settings.cpp
19
Settings.cpp
@@ -79,6 +79,12 @@ void Settings::Save()
|
||||
RenderNode->SetAttribute("res", application.render.res);
|
||||
pRoot->InsertEndChild(RenderNode);
|
||||
|
||||
// Record
|
||||
XMLElement *RecordNode = xmlDoc.NewElement( "Record" );
|
||||
RecordNode->SetAttribute("path", application.record.path.c_str());
|
||||
RecordNode->SetAttribute("profile", application.record.profile);
|
||||
pRoot->InsertEndChild(RecordNode);
|
||||
|
||||
// Transition
|
||||
XMLElement *TransitionNode = xmlDoc.NewElement( "Transition" );
|
||||
TransitionNode->SetAttribute("auto_open", application.transition.auto_open);
|
||||
@@ -219,6 +225,19 @@ void Settings::Load()
|
||||
rendernode->QueryIntAttribute("res", &application.render.res);
|
||||
}
|
||||
|
||||
|
||||
// Render
|
||||
XMLElement * recordnode = pRoot->FirstChildElement("Record");
|
||||
if (recordnode != nullptr) {
|
||||
recordnode->QueryIntAttribute("profile", &application.record.profile);
|
||||
|
||||
const char *path_ = recordnode->Attribute("path");
|
||||
if (path_)
|
||||
application.record.path = std::string(path_);
|
||||
else
|
||||
application.record.path = SystemToolkit::home_path();
|
||||
}
|
||||
|
||||
// Transition
|
||||
XMLElement * transitionnode = pRoot->FirstChildElement("Transition");
|
||||
if (transitionnode != nullptr) {
|
||||
|
||||
14
Settings.h
14
Settings.h
@@ -58,6 +58,17 @@ struct ViewConfig
|
||||
|
||||
};
|
||||
|
||||
struct RecordConfig
|
||||
{
|
||||
std::string path;
|
||||
int profile;
|
||||
|
||||
RecordConfig() : path("") {
|
||||
profile = 0;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
struct History
|
||||
{
|
||||
std::string path;
|
||||
@@ -143,6 +154,9 @@ struct Application
|
||||
// settings render
|
||||
RenderConfig render;
|
||||
|
||||
// settings render
|
||||
RecordConfig record;
|
||||
|
||||
// settings transition
|
||||
TransitionConfig transition;
|
||||
|
||||
|
||||
@@ -245,6 +245,21 @@ bool SystemToolkit::file_exists(const string& path)
|
||||
// TODO : WIN32 implementation
|
||||
}
|
||||
|
||||
|
||||
// tests if dir is a directory and return its path, empty string otherwise
|
||||
std::string SystemToolkit::path_directory(const std::string& path)
|
||||
{
|
||||
string directorypath = "";
|
||||
|
||||
DIR *dir;
|
||||
if ((dir = opendir (path.c_str())) != NULL) {
|
||||
directorypath = path + PATH_SEP;
|
||||
closedir (dir);
|
||||
}
|
||||
|
||||
return directorypath;
|
||||
}
|
||||
|
||||
list<string> SystemToolkit::list_directory(const string& path, const string& filter)
|
||||
{
|
||||
list<string> ls;
|
||||
|
||||
@@ -42,6 +42,9 @@ namespace SystemToolkit
|
||||
// extract the extension of a filename
|
||||
std::string extension_filename(const std::string& filename);
|
||||
|
||||
// tests if dir is a directory and return its path, empty string otherwise
|
||||
std::string path_directory(const std::string& path);
|
||||
|
||||
// list all files of a directory mathing the given filter extension (if any)
|
||||
std::list<std::string> list_directory(const std::string& path, const std::string& filter = "");
|
||||
|
||||
|
||||
@@ -292,7 +292,7 @@ void UserInterface::handleKeyboard()
|
||||
}
|
||||
else if (ImGui::IsKeyPressed( GLFW_KEY_R )) {
|
||||
// toggle recording
|
||||
Mixer::manager().session()->addRecorder(new H264Recorder);
|
||||
Mixer::manager().session()->addRecorder(new VideoRecorder);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -850,6 +850,14 @@ void UserInterface::RenderPreview()
|
||||
}
|
||||
};
|
||||
|
||||
// return from thread for folder openning
|
||||
static char record_browser_path_[2048] = {};
|
||||
static std::atomic<bool> record_path_selected = false;
|
||||
if (record_path_selected) {
|
||||
record_path_selected = false;
|
||||
Settings::application.record.path = std::string(record_browser_path_);
|
||||
}
|
||||
|
||||
FrameBuffer *output = Mixer::manager().session()->frame();
|
||||
if (output)
|
||||
{
|
||||
@@ -861,7 +869,11 @@ void UserInterface::RenderPreview()
|
||||
{
|
||||
ImGui::End();
|
||||
return;
|
||||
|
||||
}
|
||||
// adapt rendering if there is a recording ongoing
|
||||
Recorder *rec = Mixer::manager().session()->frontRecorder();
|
||||
|
||||
// menu (no title bar)
|
||||
if (ImGui::BeginMenuBar())
|
||||
{
|
||||
@@ -870,11 +882,40 @@ void UserInterface::RenderPreview()
|
||||
if ( ImGui::MenuItem( ICON_FA_WINDOW_RESTORE " Show output window") )
|
||||
Rendering::manager().outputWindow().show();
|
||||
|
||||
if ( ImGui::MenuItem( ICON_FA_TIMES " Close") )
|
||||
Settings::application.widget.preview = false;
|
||||
|
||||
ImGui::EndMenu();
|
||||
}
|
||||
if (ImGui::BeginMenu("Record"))
|
||||
{
|
||||
if ( ImGui::MenuItem( ICON_FA_CAMERA_RETRO " Save frame") )
|
||||
Mixer::manager().session()->addRecorder(new PNGRecorder);
|
||||
|
||||
if ( ImGui::MenuItem( ICON_FA_TIMES " Close") )
|
||||
Settings::application.widget.preview = false;
|
||||
ImGui::Separator();
|
||||
|
||||
// Stop recording menu if recording exists
|
||||
if (rec) {
|
||||
|
||||
if ( ImGui::MenuItem( ICON_FA_SQUARE " Stop Record") )
|
||||
rec->stop();
|
||||
}
|
||||
// start recording menu
|
||||
else {
|
||||
// start rec
|
||||
if ( ImGui::MenuItem( ICON_FA_CIRCLE " Record") )
|
||||
Mixer::manager().session()->addRecorder(new VideoRecorder);
|
||||
|
||||
ImGui::Combo("##RecProfile", &Settings::application.record.profile, VideoRecorder::profile_name, IM_ARRAYSIZE(VideoRecorder::profile_name) );
|
||||
|
||||
if (Settings::application.record.path.empty())
|
||||
Settings::application.record.path = SystemToolkit::home_path();
|
||||
|
||||
if (ImGui::MenuItem( Settings::application.record.path.c_str() ) ){
|
||||
std::thread (FolderDialogOpen, record_browser_path_, &record_path_selected, Settings::application.record.path).detach();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
ImGui::EndMenu();
|
||||
}
|
||||
@@ -888,12 +929,22 @@ void UserInterface::RenderPreview()
|
||||
ImVec2 draw_pos = ImGui::GetCursorScreenPos();
|
||||
// preview image
|
||||
ImGui::Image((void*)(intptr_t)output->texture(), imagesize);
|
||||
// recording indicator overlay
|
||||
if (rec)
|
||||
{
|
||||
float r = ImGui::GetTextLineHeightWithSpacing();
|
||||
ImGui::SetCursorScreenPos(ImVec2(draw_pos.x + r, draw_pos.y + r));
|
||||
ImGuiToolkit::PushFont(ImGuiToolkit::FONT_LARGE);
|
||||
ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(1.0, 0.05, 0.05, 0.8f));
|
||||
ImGui::Text(ICON_FA_CIRCLE " %s", rec->info().c_str() );
|
||||
ImGui::PopStyleColor(1);
|
||||
ImGui::PopFont();
|
||||
}
|
||||
// tooltip overlay
|
||||
if (ImGui::IsItemHovered()) {
|
||||
|
||||
if (ImGui::IsItemHovered())
|
||||
{
|
||||
ImDrawList* draw_list = ImGui::GetWindowDrawList();
|
||||
draw_list->AddRectFilled(draw_pos, ImVec2(draw_pos.x + width, draw_pos.y + ImGui::GetTextLineHeightWithSpacing()), IM_COL32(5, 5, 5, 100));
|
||||
|
||||
ImGui::SetCursorScreenPos(draw_pos);
|
||||
ImGui::Text(" %d x %d px, %d fps", output->width(), output->height(), int(1000.f / Mixer::manager().dt()) );
|
||||
}
|
||||
@@ -1157,7 +1208,7 @@ void MediaController::Render()
|
||||
center.x -= ImGui::GetTextLineHeight() * 2.f;
|
||||
center.y += ImGui::GetTextLineHeight() * 0.5f;
|
||||
ImGui::SetCursorPos(center);
|
||||
ImGui::Text("No selection");
|
||||
ImGui::Text("No media");
|
||||
ImGui::PopFont();
|
||||
ImGui::PopStyleColor(1);
|
||||
}
|
||||
|
||||
@@ -45,7 +45,7 @@
|
||||
#define IMGUI_TITLE_MEDIAPLAYER ICON_FA_FILM " Player"
|
||||
#define IMGUI_TITLE_TOOLBOX ICON_FA_WRENCH " Tools"
|
||||
#define IMGUI_TITLE_SHADEREDITOR ICON_FA_CODE " Shader Editor"
|
||||
#define IMGUI_TITLE_PREVIEW ICON_FA_DESKTOP " Display"
|
||||
#define IMGUI_TITLE_PREVIEW ICON_FA_DESKTOP " Ouput"
|
||||
#define IMGUI_TITLE_DELETE ICON_FA_BROOM " Delete?"
|
||||
#define IMGUI_LABEL_RECENT_FILES " Recent files"
|
||||
#define IMGUI_RIGHT_ALIGN -3.5f * ImGui::GetTextLineHeightWithSpacing()
|
||||
|
||||
Reference in New Issue
Block a user