mirror of
https://github.com/brunoherbelin/vimix.git
synced 2025-12-05 15:30:00 +01:00
Initial commit
This commit is contained in:
550
ImGuiToolkit.cpp
Normal file
550
ImGuiToolkit.cpp
Normal file
@@ -0,0 +1,550 @@
|
||||
|
||||
#include <map>
|
||||
#include <ctime>
|
||||
|
||||
#include "imgui.h"
|
||||
#ifndef IMGUI_DEFINE_MATH_OPERATORS
|
||||
#define IMGUI_DEFINE_MATH_OPERATORS
|
||||
#endif
|
||||
#include "imgui_internal.h"
|
||||
|
||||
#define MILISECOND 1000000##L
|
||||
#define SECOND 1000000000##L
|
||||
#define MINUTE 60000000000##L
|
||||
|
||||
#include "ResourceManager.h"
|
||||
#include "ImGuiToolkit.h"
|
||||
#include "GstToolkit.h"
|
||||
|
||||
static unsigned int textureicons = 0;
|
||||
static std::map <ImGuiToolkit::font_style, ImFont*>fontmap;
|
||||
|
||||
std::string ImGuiToolkit::DateTime(){
|
||||
std::time_t t = std::time(0); // get time now
|
||||
std::tm* now = std::localtime(&t);
|
||||
std::string datetime = std::to_string(now->tm_year + 1900) +
|
||||
std::to_string(now->tm_mon + 1) + std::to_string(now->tm_mday) +
|
||||
std::to_string(now->tm_hour) + std::to_string(now->tm_min) +
|
||||
std::to_string(now->tm_sec);
|
||||
|
||||
return datetime;
|
||||
}
|
||||
|
||||
void ImGuiToolkit::Icon(int i, int j)
|
||||
{
|
||||
// icons.dds is a 20 x 20 grid of icons
|
||||
if (textureicons == 0)
|
||||
textureicons = Resource::getTextureDDS("images/icons.dds");
|
||||
|
||||
ImVec2 uv0( static_cast<float>(i) * 0.05, static_cast<float>(j) * 0.05 );
|
||||
ImVec2 uv1( uv0.x + 0.05, uv0.y + 0.05 );
|
||||
ImGui::Image((void*)(intptr_t)textureicons, ImVec2(24, 24), uv0, uv1);
|
||||
}
|
||||
|
||||
bool ImGuiToolkit::ButtonIcon(int i, int j)
|
||||
{
|
||||
// icons.dds is a 20 x 20 grid of icons
|
||||
if (textureicons == 0)
|
||||
textureicons = Resource::getTextureDDS("images/icons.dds");
|
||||
|
||||
ImVec2 uv0( static_cast<float>(i) * 0.05, static_cast<float>(j) * 0.05 );
|
||||
ImVec2 uv1( uv0.x + 0.05, uv0.y + 0.05 );
|
||||
|
||||
ImGui::PushID( i*20 + j);
|
||||
bool ret = ImGui::ImageButton((void*)(intptr_t)textureicons, ImVec2(24,24), uv0, uv1, 3);
|
||||
ImGui::PopID();
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
void ImGuiToolkit::ShowIconsWindow(bool* p_open)
|
||||
{
|
||||
if (textureicons == 0)
|
||||
textureicons = Resource::getTextureDDS("images/icons.dds");
|
||||
|
||||
ImGuiIO& io = ImGui::GetIO();
|
||||
float my_tex_w = 640.0;
|
||||
float my_tex_h = 640.0;
|
||||
|
||||
ImGui::Begin("Icons", p_open);
|
||||
|
||||
// ImGui::Text(" 00 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19");
|
||||
|
||||
ImVec2 pos = ImGui::GetCursorScreenPos();
|
||||
ImGui::Image((void*)(intptr_t)textureicons, ImVec2(640, 640));
|
||||
if (ImGui::IsItemHovered())
|
||||
{
|
||||
float zoom = 4.0f;
|
||||
float region_sz = 32.0f; // 64 x 64 icons
|
||||
float region_x = io.MousePos.x - pos.x - region_sz * 0.5f;
|
||||
if (region_x < 0.0f) region_x = 0.0f; else if (region_x > my_tex_w - region_sz) region_x = my_tex_w - region_sz;
|
||||
float region_y = io.MousePos.y - pos.y - region_sz * 0.5f;
|
||||
if (region_y < 0.0f) region_y = 0.0f; else if (region_y > my_tex_h - region_sz) region_y = my_tex_h - region_sz;
|
||||
ImGui::BeginTooltip();
|
||||
int i = (int) ( (region_x + region_sz * 0.5f) / region_sz);
|
||||
int j = (int) ( (region_y + region_sz * 0.5f)/ region_sz);
|
||||
ImGuiToolkit::Icon(i, j);
|
||||
ImGui::SameLine();
|
||||
ImGui::Text(" Icon (%d, %d)", i, j);
|
||||
ImVec2 uv0 = ImVec2((region_x) / my_tex_w, (region_y) / my_tex_h);
|
||||
ImVec2 uv1 = ImVec2((region_x + region_sz) / my_tex_w, (region_y + region_sz) / my_tex_h);
|
||||
ImGui::Image((void*)(intptr_t)textureicons, ImVec2(region_sz * zoom, region_sz * zoom), uv0, uv1, ImVec4(1.0f, 1.0f, 1.0f, 1.0f), ImVec4(1.0f, 1.0f, 1.0f, 0.5f));
|
||||
ImGui::EndTooltip();
|
||||
}
|
||||
|
||||
ImGui::End();
|
||||
}
|
||||
|
||||
bool ImGuiToolkit::TimelineSlider(const char* label, guint64 *time, guint64 begin, guint64 end, guint64 duration, guint64 step)
|
||||
{
|
||||
static guint64 optimal_tick_marks[] = { 100 * MILISECOND, 500 * MILISECOND, 1 * SECOND, 2 * SECOND, 5 * SECOND, 10 * SECOND, 20 * SECOND, 1 * MINUTE, 2 * MINUTE, 5 * MINUTE, 10 * MINUTE, 60 * MINUTE };
|
||||
|
||||
|
||||
bool value_changed = false;
|
||||
|
||||
// get window
|
||||
ImGuiWindow* window = ImGui::GetCurrentWindow();
|
||||
if (window->SkipItems)
|
||||
return value_changed;
|
||||
|
||||
// get style
|
||||
const ImGuiStyle& style = ImGui::GetStyle();
|
||||
const float fontsize = ImGui::GetFontSize();
|
||||
ImGuiContext& g = *GImGui;
|
||||
const ImGuiID id = window->GetID(label);
|
||||
|
||||
// widget bounding box
|
||||
ImVec2 pos = window->DC.CursorPos;
|
||||
ImVec2 size = ImGui::CalcItemSize(ImVec2(-FLT_MIN, 0.0f), ImGui::CalcItemWidth(), (fontsize + style.FramePadding.y)* 2.0f);
|
||||
ImRect bbox(pos, pos + size);
|
||||
ImGui::ItemSize(size, style.FramePadding.y);
|
||||
if (!ImGui::ItemAdd(bbox, id))
|
||||
return value_changed;
|
||||
|
||||
// cursor size
|
||||
const float cursor_scale = 1.f;
|
||||
const float cursor_width = window->DrawList->_Data->FontSize * cursor_scale;
|
||||
|
||||
// user input
|
||||
const bool hovered = ImGui::ItemHoverable(bbox, id);
|
||||
bool temp_input_is_active = ImGui::TempInputTextIsActive(id);
|
||||
if (!temp_input_is_active)
|
||||
{
|
||||
const bool focus_requested = ImGui::FocusableItemRegister(window, id);
|
||||
const bool clicked = (hovered && g.IO.MouseClicked[0]);
|
||||
if (focus_requested || clicked || g.NavActivateId == id || g.NavInputId == id)
|
||||
{
|
||||
ImGui::SetActiveID(id, window);
|
||||
ImGui::SetFocusID(id, window);
|
||||
ImGui::FocusWindow(window);
|
||||
}
|
||||
}
|
||||
|
||||
// Render bounding box
|
||||
const ImU32 frame_col = ImGui::GetColorU32(g.ActiveId == id ? ImGuiCol_FrameBgActive : g.HoveredId == id ? ImGuiCol_FrameBgHovered : ImGuiCol_FrameBg);
|
||||
ImGui::RenderFrame(bbox.Min, bbox.Max, frame_col, true, style.FrameRounding);
|
||||
|
||||
// draw inside a slightly smaller bounding box
|
||||
ImRect timeline_bbox(bbox);
|
||||
timeline_bbox.Expand(ImVec2(-cursor_width, 0.f));
|
||||
|
||||
// units conversion: from time to float (calculation made with higher precision first)
|
||||
float time_ = static_cast<float> ( static_cast<double>(*time) / static_cast<double>(duration) );
|
||||
float begin_ = static_cast<float> ( static_cast<double>(begin) / static_cast<double>(duration) );
|
||||
float end_ = static_cast<float> ( static_cast<double>(end) / static_cast<double>(duration) );
|
||||
float step_ = static_cast<float> ( static_cast<double>(step) / static_cast<double>(duration) );
|
||||
|
||||
// by default, put a tick mark at every frame step and a large mark every second
|
||||
guint64 tick_step = step;
|
||||
guint64 large_tick_step = SECOND;
|
||||
|
||||
// how many pixels to represent one frame step?
|
||||
float tick_step_pixels = timeline_bbox.GetWidth() * step_;
|
||||
// while there is less than 3 pixels between two tick marks (or at last optimal tick mark)
|
||||
for ( int i=0; i<10 && tick_step_pixels < 3.f; ++i )
|
||||
{
|
||||
tick_step = optimal_tick_marks[i];
|
||||
large_tick_step = optimal_tick_marks[i+1];
|
||||
tick_step_pixels = timeline_bbox.GetWidth() * static_cast<float> ( static_cast<double>(tick_step) / static_cast<double>(duration) );
|
||||
}
|
||||
|
||||
// static guint64 t = 0;
|
||||
// if (t != *time) {
|
||||
// t = *time;
|
||||
// ImGuiToolkit::Log("tic %ld %ld %ld %ld \n", duration, step, tick_step, large_tick_step);
|
||||
// }
|
||||
|
||||
// tick marks along timeline
|
||||
ImU32 color = ImGui::GetColorU32( style.Colors[ImGuiCol_PlotLines] );
|
||||
pos = timeline_bbox.GetTL();
|
||||
guint64 tick = 0;
|
||||
while ( tick < duration)
|
||||
{
|
||||
// large tick mark every large tick
|
||||
float tick_length = !(tick%large_tick_step) ? fontsize : style.FramePadding.y;
|
||||
// draw a tick mark each step
|
||||
window->DrawList->AddLine( pos, pos + ImVec2(0.f, tick_length), color);
|
||||
|
||||
// next tick
|
||||
tick += tick_step;
|
||||
float tick_percent = static_cast<float> ( static_cast<double>(tick) / static_cast<double>(duration) );
|
||||
pos = ImLerp(timeline_bbox.GetTL(), timeline_bbox.GetTR(), tick_percent);
|
||||
}
|
||||
// end
|
||||
window->DrawList->AddLine( timeline_bbox.GetTR(), timeline_bbox.GetTR() + ImVec2(0.f, fontsize), color);
|
||||
|
||||
// render text : duration and current time
|
||||
char overlay_buf[24];
|
||||
ImVec2 overlay_size = ImVec2(0.f, 0.f);
|
||||
ImFormatString(overlay_buf, IM_ARRAYSIZE(overlay_buf), "%s", GstToolkit::to_string(duration).c_str());
|
||||
overlay_size = ImGui::CalcTextSize(overlay_buf, NULL);
|
||||
overlay_size += ImVec2(3.f, 3.f);
|
||||
if (overlay_size.x > 0.0f)
|
||||
ImGui::RenderTextClipped( bbox.GetBR() - overlay_size, bbox.Max, overlay_buf, NULL, &overlay_size);
|
||||
|
||||
ImFormatString(overlay_buf, IM_ARRAYSIZE(overlay_buf), "%s", GstToolkit::to_string(*time).c_str());
|
||||
overlay_size = ImGui::CalcTextSize(overlay_buf, NULL);
|
||||
overlay_size = ImVec2(3.f, -3.f - overlay_size.y);
|
||||
if (overlay_size.x > 0.0f)
|
||||
ImGui::RenderTextClipped( bbox.GetBL() + overlay_size, bbox.Max, overlay_buf, NULL, &overlay_size);
|
||||
|
||||
|
||||
// Slider behavior
|
||||
ImRect grab_bb;
|
||||
ImRect slider_bbox( timeline_bbox.GetTL() + ImVec2(-cursor_width * 0.5f, cursor_width ), timeline_bbox.GetBR() + ImVec2( cursor_width * 0.5f, 0.f ) );
|
||||
float time_slider = time_;
|
||||
float time_zero = 0.f;
|
||||
float time_end = 1.f;
|
||||
value_changed = ImGui::SliderBehavior(slider_bbox, id, ImGuiDataType_Float, &time_slider, &time_zero,
|
||||
&time_end, "%.2f", 1.f, ImGuiSliderFlags_None, &grab_bb);
|
||||
if (value_changed){
|
||||
//ImGuiToolkit::Log("slider %f %ld \n", time_slider, static_cast<guint64> ( static_cast<double>(time_slider) * static_cast<double>(duration) ));
|
||||
*time = static_cast<guint64> ( static_cast<double>(time_slider) * static_cast<double>(duration) );
|
||||
}
|
||||
|
||||
// Render grab
|
||||
if (grab_bb.Max.x > grab_bb.Min.x)
|
||||
window->DrawList->AddRectFilled(grab_bb.Min, grab_bb.Max,
|
||||
ImGui::GetColorU32(g.ActiveId == id ? ImGuiCol_SliderGrabActive : ImGuiCol_SliderGrab),
|
||||
style.GrabRounding);
|
||||
|
||||
// last: draw the cursor
|
||||
color = ImGui::GetColorU32(style.Colors[ImGuiCol_SliderGrab]);
|
||||
pos = ImLerp(timeline_bbox.GetTL(), timeline_bbox.GetTR(), time_) - ImVec2(cursor_width * 0.5f, 0.f);
|
||||
ImGui::RenderArrow(window->DrawList, pos, color, ImGuiDir_Up, cursor_scale);
|
||||
|
||||
return value_changed;
|
||||
}
|
||||
|
||||
|
||||
void ImGuiToolkit::Bar(float value, float in, float out, float min, float max, const char* title, bool expand)
|
||||
{
|
||||
ImGuiWindow* window = ImGui::GetCurrentWindow();
|
||||
if (window->SkipItems)
|
||||
return;
|
||||
|
||||
ImGuiContext& g = *GImGui;
|
||||
const ImGuiStyle& style = g.Style;
|
||||
|
||||
ImVec2 size_arg = expand ? ImVec2(-FLT_MIN, 0.0f) : ImVec2(0.0f, 0.0f);
|
||||
ImVec2 pos = window->DC.CursorPos;
|
||||
ImVec2 size = ImGui::CalcItemSize(size_arg, ImGui::CalcItemWidth(), (g.FontSize + style.FramePadding.y)* 2.0f);
|
||||
ImRect bb(pos, pos + size);
|
||||
ImGui::ItemSize(size, style.FramePadding.y);
|
||||
if (!ImGui::ItemAdd(bb, 0))
|
||||
return;
|
||||
|
||||
// calculations
|
||||
float range_in = in / (max - min) + min;
|
||||
float range_out = out / (max - min) + min;
|
||||
float slider = value / (max - min) + min;
|
||||
slider = ImSaturate(slider);
|
||||
|
||||
// Render
|
||||
ImGui::RenderFrame(bb.Min, bb.Max, ImGui::GetColorU32(ImGuiCol_FrameBg), true, style.FrameRounding);
|
||||
|
||||
// tics
|
||||
const ImU32 col_base = ImGui::GetColorU32( ImGuiCol_PlotLines );
|
||||
|
||||
|
||||
ImVec2 pos0 = ImLerp(bb.Min, bb.Max, 0.1);
|
||||
ImVec2 pos1 = ImLerp(bb.Min, bb.Max, 0.2);
|
||||
float step = (bb.Max.x - bb.Min.x) / 100.f;
|
||||
int i = 0;
|
||||
for (float tic = bb.Min.x; tic < bb.Max.x; tic += step, ++i) {
|
||||
float tic_lengh = i % 10 ? style.FramePadding.y : g.FontSize;
|
||||
window->DrawList->AddLine( ImVec2(tic, bb.Min.y), ImVec2(tic, bb.Min.y + tic_lengh), col_base);
|
||||
}
|
||||
|
||||
bb.Min.y += g.FontSize;
|
||||
bb.Expand(ImVec2(-style.FrameBorderSize, -style.FrameBorderSize));
|
||||
const ImVec2 fill_br = ImVec2(ImLerp(bb.Min.x, bb.Max.x, slider), bb.Max.y);
|
||||
|
||||
ImGui::RenderRectFilledRangeH(window->DrawList, bb, ImGui::GetColorU32(ImGuiCol_CheckMark), range_in, range_out, style.FrameRounding);
|
||||
|
||||
char overlay_buf[32];
|
||||
ImVec2 overlay_size = ImVec2(0.f, 0.f);
|
||||
ImFormatString(overlay_buf, IM_ARRAYSIZE(overlay_buf), "%.0f", in);
|
||||
overlay_size = ImGui::CalcTextSize(overlay_buf, NULL);
|
||||
if (overlay_size.x > 0.0f)
|
||||
ImGui::RenderTextClipped(ImVec2( ImClamp(fill_br.x + style.ItemSpacing.x,bb.Min.x, bb.Max.x - overlay_size.x - style.ItemInnerSpacing.x), bb.Min.y),
|
||||
bb.Max, title, NULL, &overlay_size, ImVec2(0.0f,0.5f), &bb);
|
||||
|
||||
|
||||
// Draw handle to move cursor (seek position)
|
||||
// ImRect grab_bb;
|
||||
// grab_bb.Min.x =
|
||||
// window->DrawList->AddRectFilled(grab_bb.Min, grab_bb.Max, ImGui::GetColorU32(ImGuiCol_SliderGrabActive), style.GrabRounding);
|
||||
|
||||
// Draw cursor (actual position)
|
||||
ImU32 color = ImGui::GetColorU32(1 ? ImGuiCol_Text : ImGuiCol_TextDisabled);
|
||||
ImGui::RenderArrow(window->DrawList, pos0, color, ImGuiDir_Up);
|
||||
|
||||
|
||||
// window->DrawList->AddRectFilled(grab_bb.Min, grab_bb.Max, ImGui::GetColorU32(g.ActiveId == id ? ImGuiCol_SliderGrabActive : ImGuiCol_SliderGrab), style.GrabRounding);
|
||||
|
||||
|
||||
// Default displaying the fraction as percentage string, but user can override it
|
||||
// char overlay_buf[32];
|
||||
// if (!title)
|
||||
// {
|
||||
// ImFormatString(overlay_buf, IM_ARRAYSIZE(overlay_buf), "%.0f", fraction*100+0.01f);
|
||||
// title = overlay_buf;
|
||||
// }
|
||||
|
||||
// ImVec2 overlay_size = ImGui::CalcTextSize(title, NULL);
|
||||
// if (overlay_size.x > 0.0f)
|
||||
// ImGui::RenderTextClipped(ImVec2( ImClamp(fill_br.x + style.ItemSpacing.x,bb.Min.x, bb.Max.x - overlay_size.x - style.ItemInnerSpacing.x), bb.Min.y),
|
||||
// bb.Max, title, NULL, &overlay_size, ImVec2(0.0f,0.5f), &bb);
|
||||
}
|
||||
|
||||
|
||||
void ImGuiToolkit::SetFont(ImGuiToolkit::font_style style, const std::string &ttf_font_name, int pointsize)
|
||||
{
|
||||
// Font Atlas ImGui Management
|
||||
ImGuiIO& io = ImGui::GetIO();
|
||||
const ImWchar* glyph_ranges = io.Fonts->GetGlyphRangesDefault();
|
||||
std::string filename = "fonts/" + ttf_font_name + ".ttf";
|
||||
std::string fontname = ttf_font_name + ", " + std::to_string(pointsize) + "px";
|
||||
ImFontConfig font_config;
|
||||
fontname.copy(font_config.Name, 40);
|
||||
font_config.FontDataOwnedByAtlas = false; // data will be copied in font atlas
|
||||
font_config.OversampleH = 7;
|
||||
font_config.OversampleV = 3;
|
||||
|
||||
// read font in Resource manager
|
||||
size_t data_size = 0;
|
||||
const char* data_src = Resource::getData(filename, &data_size);
|
||||
|
||||
// temporary copy for instanciation
|
||||
void *data = malloc( sizeof (char) * data_size);
|
||||
memcpy(data, data_src, data_size);
|
||||
|
||||
// create and add font
|
||||
fontmap[style] = io.Fonts->AddFontFromMemoryTTF(data, (int)data_size, static_cast<float>(pointsize), &font_config, glyph_ranges);
|
||||
free(data);
|
||||
|
||||
// merge in icons from Font Awesome
|
||||
{
|
||||
// range for only FontAwesome characters
|
||||
static const ImWchar icons_ranges[] = { ICON_MIN_FA, ICON_MAX_FA, 0 };
|
||||
font_config.MergeMode = true; // Merging glyphs into current font
|
||||
font_config.PixelSnapH = true;
|
||||
fontname = "icons" + fontname;
|
||||
fontname.copy(font_config.Name, 40);
|
||||
// load FontAwesome only once
|
||||
static size_t icons_data_size = 0;
|
||||
static const char* icons_data_src = Resource::getData( "fonts/" FONT_ICON_FILE_NAME_FAS, &icons_data_size);
|
||||
// temporary copy for instanciation
|
||||
void *icons_data = malloc( sizeof (char) * icons_data_size);
|
||||
memcpy(icons_data, icons_data_src, icons_data_size);
|
||||
// merge font
|
||||
io.Fonts->AddFontFromMemoryTTF(icons_data, (int)icons_data_size, static_cast<float>(pointsize-2), &font_config, icons_ranges);
|
||||
free(icons_data);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void ImGuiToolkit::PushFont(ImGuiToolkit::font_style style)
|
||||
{
|
||||
if (fontmap.count(style) > 0)
|
||||
ImGui::PushFont( fontmap[style] );
|
||||
else
|
||||
ImGui::PushFont( NULL );
|
||||
}
|
||||
|
||||
void ImGuiToolkit::SetAccentColor(accent_color color)
|
||||
{
|
||||
ImVec4* colors = ImGui::GetStyle().Colors;
|
||||
|
||||
if (color == ImGuiToolkit::ACCENT_ORANGE) {
|
||||
|
||||
colors[ImGuiCol_Text] = ImVec4(1.00f, 1.00f, 1.00f, 1.00f);
|
||||
colors[ImGuiCol_TextDisabled] = ImVec4(0.55f, 0.55f, 0.55f, 1.00f);
|
||||
colors[ImGuiCol_WindowBg] = ImVec4(0.13f, 0.13f, 0.14f, 0.94f);
|
||||
colors[ImGuiCol_ChildBg] = ImVec4(0.00f, 0.00f, 0.00f, 0.00f);
|
||||
colors[ImGuiCol_PopupBg] = ImVec4(0.08f, 0.08f, 0.08f, 0.97f);
|
||||
colors[ImGuiCol_Border] = ImVec4(0.69f, 0.69f, 0.69f, 0.25f);
|
||||
colors[ImGuiCol_BorderShadow] = ImVec4(0.00f, 0.00f, 0.00f, 0.00f);
|
||||
colors[ImGuiCol_FrameBg] = ImVec4(0.39f, 0.39f, 0.39f, 0.55f);
|
||||
colors[ImGuiCol_FrameBgHovered] = ImVec4(0.22f, 0.22f, 0.22f, 1.00f);
|
||||
colors[ImGuiCol_FrameBgActive] = ImVec4(0.22f, 0.22f, 0.22f, 0.78f);
|
||||
colors[ImGuiCol_TitleBg] = ImVec4(0.14f, 0.14f, 0.14f, 0.94f);
|
||||
colors[ImGuiCol_TitleBgActive] = ImVec4(0.26f, 0.26f, 0.26f, 1.00f);
|
||||
colors[ImGuiCol_TitleBgCollapsed] = ImVec4(0.00f, 0.00f, 0.00f, 0.51f);
|
||||
colors[ImGuiCol_MenuBarBg] = ImVec4(0.36f, 0.36f, 0.36f, 0.62f);
|
||||
colors[ImGuiCol_ScrollbarBg] = ImVec4(0.02f, 0.02f, 0.02f, 0.53f);
|
||||
colors[ImGuiCol_ScrollbarGrab] = ImVec4(0.31f, 0.31f, 0.31f, 1.00f);
|
||||
colors[ImGuiCol_ScrollbarGrabHovered] = ImVec4(0.41f, 0.41f, 0.41f, 1.00f);
|
||||
colors[ImGuiCol_ScrollbarGrabActive] = ImVec4(0.51f, 0.51f, 0.51f, 1.00f);
|
||||
colors[ImGuiCol_CheckMark] = ImVec4(1.00f, 0.63f, 0.31f, 1.00f);
|
||||
colors[ImGuiCol_SliderGrab] = ImVec4(0.88f, 0.52f, 0.24f, 1.00f);
|
||||
colors[ImGuiCol_SliderGrabActive] = ImVec4(0.98f, 0.59f, 0.26f, 1.00f);
|
||||
colors[ImGuiCol_Button] = ImVec4(0.47f, 0.47f, 0.47f, 0.72f);
|
||||
colors[ImGuiCol_ButtonHovered] = ImVec4(0.24f, 0.24f, 0.24f, 0.90f);
|
||||
colors[ImGuiCol_ButtonActive] = ImVec4(0.24f, 0.24f, 0.24f, 0.67f);
|
||||
colors[ImGuiCol_Header] = ImVec4(0.98f, 0.59f, 0.26f, 0.31f);
|
||||
colors[ImGuiCol_HeaderHovered] = ImVec4(0.98f, 0.59f, 0.26f, 0.51f);
|
||||
colors[ImGuiCol_HeaderActive] = ImVec4(0.98f, 0.59f, 0.26f, 1.00f);
|
||||
colors[ImGuiCol_Separator] = ImVec4(0.50f, 0.43f, 0.43f, 0.50f);
|
||||
colors[ImGuiCol_SeparatorHovered] = ImVec4(0.75f, 0.40f, 0.10f, 0.67f);
|
||||
colors[ImGuiCol_SeparatorActive] = ImVec4(0.90f, 0.73f, 0.59f, 0.95f);
|
||||
colors[ImGuiCol_ResizeGrip] = ImVec4(0.49f, 0.49f, 0.52f, 0.50f);
|
||||
colors[ImGuiCol_ResizeGripHovered] = ImVec4(0.90f, 0.73f, 0.59f, 0.67f);
|
||||
colors[ImGuiCol_ResizeGripActive] = ImVec4(0.90f, 0.73f, 0.59f, 0.95f);
|
||||
colors[ImGuiCol_Tab] = ImVec4(0.58f, 0.35f, 0.18f, 0.82f);
|
||||
colors[ImGuiCol_TabHovered] = ImVec4(0.80f, 0.49f, 0.25f, 0.82f);
|
||||
colors[ImGuiCol_TabActive] = ImVec4(0.80f, 0.49f, 0.25f, 1.00f);
|
||||
colors[ImGuiCol_TabUnfocused] = ImVec4(0.15f, 0.10f, 0.07f, 0.97f);
|
||||
colors[ImGuiCol_TabUnfocusedActive] = ImVec4(0.42f, 0.26f, 0.14f, 1.00f);
|
||||
colors[ImGuiCol_PlotLines] = ImVec4(0.61f, 0.61f, 0.61f, 1.00f);
|
||||
colors[ImGuiCol_PlotLinesHovered] = ImVec4(1.00f, 0.43f, 0.35f, 1.00f);
|
||||
colors[ImGuiCol_PlotHistogram] = ImVec4(0.94f, 0.57f, 0.01f, 1.00f);
|
||||
colors[ImGuiCol_PlotHistogramHovered] = ImVec4(1.00f, 0.82f, 0.00f, 1.00f);
|
||||
colors[ImGuiCol_TextSelectedBg] = ImVec4(0.98f, 0.59f, 0.26f, 0.64f);
|
||||
colors[ImGuiCol_DragDropTarget] = ImVec4(1.00f, 1.00f, 0.00f, 0.90f);
|
||||
colors[ImGuiCol_NavHighlight] = ImVec4(0.98f, 0.59f, 0.26f, 1.00f);
|
||||
colors[ImGuiCol_NavWindowingHighlight] = ImVec4(1.00f, 1.00f, 1.00f, 0.70f);
|
||||
colors[ImGuiCol_NavWindowingDimBg] = ImVec4(0.80f, 0.80f, 0.80f, 0.13f);
|
||||
colors[ImGuiCol_ModalWindowDimBg] = ImVec4(0.10f, 0.10f, 0.10f, 0.60f);
|
||||
|
||||
}
|
||||
else if (color == ImGuiToolkit::ACCENT_GREY) {
|
||||
colors[ImGuiCol_Text] = ImVec4(1.00f, 1.00f, 1.00f, 1.00f);
|
||||
colors[ImGuiCol_TextDisabled] = ImVec4(0.55f, 0.55f, 0.55f, 1.00f);
|
||||
colors[ImGuiCol_WindowBg] = ImVec4(0.13f, 0.13f, 0.14f, 0.94f);
|
||||
colors[ImGuiCol_ChildBg] = ImVec4(0.00f, 0.00f, 0.00f, 0.00f);
|
||||
colors[ImGuiCol_PopupBg] = ImVec4(0.08f, 0.08f, 0.08f, 0.97f);
|
||||
colors[ImGuiCol_Border] = ImVec4(0.69f, 0.69f, 0.69f, 0.25f);
|
||||
colors[ImGuiCol_BorderShadow] = ImVec4(0.00f, 0.00f, 0.00f, 0.00f);
|
||||
colors[ImGuiCol_FrameBg] = ImVec4(0.39f, 0.39f, 0.39f, 0.55f);
|
||||
colors[ImGuiCol_FrameBgHovered] = ImVec4(0.39f, 0.39f, 0.39f, 1.00f);
|
||||
colors[ImGuiCol_FrameBgActive] = ImVec4(0.39f, 0.39f, 0.39f, 0.78f);
|
||||
colors[ImGuiCol_TitleBg] = ImVec4(0.14f, 0.14f, 0.14f, 0.94f);
|
||||
colors[ImGuiCol_TitleBgActive] = ImVec4(0.26f, 0.26f, 0.26f, 1.00f);
|
||||
colors[ImGuiCol_TitleBgCollapsed] = ImVec4(0.00f, 0.00f, 0.00f, 0.51f);
|
||||
colors[ImGuiCol_MenuBarBg] = ImVec4(0.36f, 0.36f, 0.36f, 0.62f);
|
||||
colors[ImGuiCol_ScrollbarBg] = ImVec4(0.02f, 0.02f, 0.02f, 0.53f);
|
||||
colors[ImGuiCol_ScrollbarGrab] = ImVec4(0.31f, 0.31f, 0.31f, 1.00f);
|
||||
colors[ImGuiCol_ScrollbarGrabHovered] = ImVec4(0.41f, 0.41f, 0.41f, 1.00f);
|
||||
colors[ImGuiCol_ScrollbarGrabActive] = ImVec4(0.51f, 0.51f, 0.51f, 1.00f);
|
||||
colors[ImGuiCol_CheckMark] = ImVec4(0.63f, 0.63f, 0.63f, 1.00f);
|
||||
colors[ImGuiCol_SliderGrab] = ImVec4(0.52f, 0.52f, 0.52f, 1.00f);
|
||||
colors[ImGuiCol_SliderGrabActive] = ImVec4(0.98f, 0.98f, 0.98f, 1.00f);
|
||||
colors[ImGuiCol_Button] = ImVec4(0.47f, 0.47f, 0.47f, 0.72f);
|
||||
colors[ImGuiCol_ButtonHovered] = ImVec4(0.24f, 0.24f, 0.24f, 0.90f);
|
||||
colors[ImGuiCol_ButtonActive] = ImVec4(0.24f, 0.24f, 0.24f, 0.67f);
|
||||
colors[ImGuiCol_Header] = ImVec4(0.59f, 0.59f, 0.59f, 0.31f);
|
||||
colors[ImGuiCol_HeaderHovered] = ImVec4(0.59f, 0.59f, 0.59f, 0.51f);
|
||||
colors[ImGuiCol_HeaderActive] = ImVec4(0.59f, 0.59f, 0.59f, 1.00f);
|
||||
colors[ImGuiCol_Separator] = ImVec4(0.43f, 0.43f, 0.50f, 0.50f);
|
||||
colors[ImGuiCol_SeparatorHovered] = ImVec4(0.75f, 0.75f, 0.75f, 0.67f);
|
||||
colors[ImGuiCol_SeparatorActive] = ImVec4(0.75f, 0.75f, 0.75f, 0.95f);
|
||||
colors[ImGuiCol_ResizeGrip] = ImVec4(0.49f, 0.49f, 0.52f, 0.50f);
|
||||
colors[ImGuiCol_ResizeGripHovered] = ImVec4(0.90f, 0.90f, 0.90f, 0.67f);
|
||||
colors[ImGuiCol_ResizeGripActive] = ImVec4(0.90f, 0.90f, 0.90f, 0.95f);
|
||||
colors[ImGuiCol_Tab] = ImVec4(0.47f, 0.47f, 0.47f, 0.82f);
|
||||
colors[ImGuiCol_TabHovered] = ImVec4(0.39f, 0.39f, 0.39f, 0.82f);
|
||||
colors[ImGuiCol_TabActive] = ImVec4(0.47f, 0.47f, 0.47f, 1.00f);
|
||||
colors[ImGuiCol_TabUnfocused] = ImVec4(0.10f, 0.10f, 0.10f, 0.97f);
|
||||
colors[ImGuiCol_TabUnfocusedActive] = ImVec4(0.14f, 0.14f, 0.14f, 1.00f);
|
||||
colors[ImGuiCol_PlotLines] = ImVec4(0.61f, 0.61f, 0.61f, 1.00f);
|
||||
colors[ImGuiCol_PlotLinesHovered] = ImVec4(1.00f, 0.43f, 0.35f, 1.00f);
|
||||
colors[ImGuiCol_PlotHistogram] = ImVec4(0.94f, 0.57f, 0.01f, 1.00f);
|
||||
colors[ImGuiCol_PlotHistogramHovered] = ImVec4(1.00f, 0.82f, 0.00f, 1.00f);
|
||||
colors[ImGuiCol_TextSelectedBg] = ImVec4(0.26f, 0.59f, 0.98f, 0.64f);
|
||||
colors[ImGuiCol_DragDropTarget] = ImVec4(1.00f, 1.00f, 0.00f, 0.90f);
|
||||
colors[ImGuiCol_NavHighlight] = ImVec4(0.26f, 0.59f, 0.98f, 1.00f);
|
||||
colors[ImGuiCol_NavWindowingHighlight] = ImVec4(1.00f, 1.00f, 1.00f, 0.70f);
|
||||
colors[ImGuiCol_NavWindowingDimBg] = ImVec4(0.80f, 0.80f, 0.80f, 0.13f);
|
||||
colors[ImGuiCol_ModalWindowDimBg] = ImVec4(0.10f, 0.10f, 0.10f, 0.60f);
|
||||
}
|
||||
else {
|
||||
// default BLUE
|
||||
colors[ImGuiCol_Text] = ImVec4(1.00f, 1.00f, 1.00f, 1.00f);
|
||||
colors[ImGuiCol_TextDisabled] = ImVec4(0.55f, 0.55f, 0.55f, 1.00f);
|
||||
colors[ImGuiCol_WindowBg] = ImVec4(0.13f, 0.13f, 0.14f, 0.94f);
|
||||
colors[ImGuiCol_ChildBg] = ImVec4(0.00f, 0.00f, 0.00f, 0.00f);
|
||||
colors[ImGuiCol_PopupBg] = ImVec4(0.08f, 0.08f, 0.08f, 0.97f);
|
||||
colors[ImGuiCol_Border] = ImVec4(0.69f, 0.69f, 0.69f, 0.25f);
|
||||
colors[ImGuiCol_BorderShadow] = ImVec4(0.00f, 0.00f, 0.00f, 0.00f);
|
||||
colors[ImGuiCol_FrameBg] = ImVec4(0.39f, 0.39f, 0.39f, 0.55f);
|
||||
colors[ImGuiCol_FrameBgHovered] = ImVec4(0.22f, 0.22f, 0.22f, 1.00f);
|
||||
colors[ImGuiCol_FrameBgActive] = ImVec4(0.22f, 0.22f, 0.22f, 0.78f);
|
||||
colors[ImGuiCol_TitleBg] = ImVec4(0.14f, 0.14f, 0.14f, 0.94f);
|
||||
colors[ImGuiCol_TitleBgActive] = ImVec4(0.26f, 0.26f, 0.26f, 1.00f);
|
||||
colors[ImGuiCol_TitleBgCollapsed] = ImVec4(0.00f, 0.00f, 0.00f, 0.51f);
|
||||
colors[ImGuiCol_MenuBarBg] = ImVec4(0.36f, 0.36f, 0.36f, 0.62f);
|
||||
colors[ImGuiCol_ScrollbarBg] = ImVec4(0.02f, 0.02f, 0.02f, 0.53f);
|
||||
colors[ImGuiCol_ScrollbarGrab] = ImVec4(0.31f, 0.31f, 0.31f, 1.00f);
|
||||
colors[ImGuiCol_ScrollbarGrabHovered] = ImVec4(0.41f, 0.41f, 0.41f, 1.00f);
|
||||
colors[ImGuiCol_ScrollbarGrabActive] = ImVec4(0.51f, 0.51f, 0.51f, 1.00f);
|
||||
colors[ImGuiCol_CheckMark] = ImVec4(0.31f, 0.63f, 1.00f, 1.00f);
|
||||
colors[ImGuiCol_SliderGrab] = ImVec4(0.24f, 0.52f, 0.88f, 1.00f);
|
||||
colors[ImGuiCol_SliderGrabActive] = ImVec4(0.26f, 0.59f, 0.98f, 1.00f);
|
||||
colors[ImGuiCol_Button] = ImVec4(0.47f, 0.47f, 0.47f, 0.72f);
|
||||
colors[ImGuiCol_ButtonHovered] = ImVec4(0.24f, 0.24f, 0.24f, 0.90f);
|
||||
colors[ImGuiCol_ButtonActive] = ImVec4(0.24f, 0.24f, 0.24f, 0.67f);
|
||||
colors[ImGuiCol_Header] = ImVec4(0.26f, 0.59f, 0.98f, 0.31f);
|
||||
colors[ImGuiCol_HeaderHovered] = ImVec4(0.26f, 0.59f, 0.98f, 0.51f);
|
||||
colors[ImGuiCol_HeaderActive] = ImVec4(0.26f, 0.59f, 0.98f, 1.00f);
|
||||
colors[ImGuiCol_Separator] = ImVec4(0.43f, 0.43f, 0.50f, 0.50f);
|
||||
colors[ImGuiCol_SeparatorHovered] = ImVec4(0.10f, 0.40f, 0.75f, 0.67f);
|
||||
colors[ImGuiCol_SeparatorActive] = ImVec4(0.59f, 0.73f, 0.90f, 0.95f);
|
||||
colors[ImGuiCol_ResizeGrip] = ImVec4(0.49f, 0.49f, 0.52f, 0.50f);
|
||||
colors[ImGuiCol_ResizeGripHovered] = ImVec4(0.59f, 0.73f, 0.90f, 0.67f);
|
||||
colors[ImGuiCol_ResizeGripActive] = ImVec4(0.59f, 0.73f, 0.90f, 0.95f);
|
||||
colors[ImGuiCol_Tab] = ImVec4(0.18f, 0.35f, 0.58f, 0.82f);
|
||||
colors[ImGuiCol_TabHovered] = ImVec4(0.25f, 0.49f, 0.80f, 0.82f);
|
||||
colors[ImGuiCol_TabActive] = ImVec4(0.25f, 0.49f, 0.80f, 1.00f);
|
||||
colors[ImGuiCol_TabUnfocused] = ImVec4(0.07f, 0.10f, 0.15f, 0.97f);
|
||||
colors[ImGuiCol_TabUnfocusedActive] = ImVec4(0.14f, 0.26f, 0.42f, 1.00f);
|
||||
colors[ImGuiCol_PlotLines] = ImVec4(0.61f, 0.61f, 0.61f, 1.00f);
|
||||
colors[ImGuiCol_PlotLinesHovered] = ImVec4(1.00f, 0.43f, 0.35f, 1.00f);
|
||||
colors[ImGuiCol_PlotHistogram] = ImVec4(0.94f, 0.57f, 0.01f, 1.00f);
|
||||
colors[ImGuiCol_PlotHistogramHovered] = ImVec4(1.00f, 0.82f, 0.00f, 1.00f);
|
||||
colors[ImGuiCol_TextSelectedBg] = ImVec4(0.26f, 0.59f, 0.98f, 0.64f);
|
||||
colors[ImGuiCol_DragDropTarget] = ImVec4(1.00f, 1.00f, 0.00f, 0.90f);
|
||||
colors[ImGuiCol_NavHighlight] = ImVec4(0.26f, 0.59f, 0.98f, 1.00f);
|
||||
colors[ImGuiCol_NavWindowingHighlight] = ImVec4(1.00f, 1.00f, 1.00f, 0.70f);
|
||||
colors[ImGuiCol_NavWindowingDimBg] = ImVec4(0.80f, 0.80f, 0.80f, 0.13f);
|
||||
colors[ImGuiCol_ModalWindowDimBg] = ImVec4(0.10f, 0.10f, 0.10f, 0.60f);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
void SetButtonHighlightColor()
|
||||
{
|
||||
ImGui::PushStyleColor( ImGuiCol_Button, (ImVec4)ImColor::HSV( 0.35f, 0.6f, 0.6f ) );
|
||||
ImGui::PushStyleColor( ImGuiCol_ButtonHovered, (ImVec4)ImColor::HSV( 0.35f, 0.8f, 0.8f ) );
|
||||
ImGui::PushStyleColor( ImGuiCol_ButtonActive, (ImVec4)ImColor::HSV( 0.35f, 0.7f, 0.7f ) );
|
||||
}
|
||||
|
||||
void ToggleButton( const char* label, bool& toggle )
|
||||
{
|
||||
const auto active = toggle;
|
||||
if( active ) SetButtonHighlightColor();
|
||||
if( ImGui::Button( label ) ) toggle = !toggle;
|
||||
if( active ) ImGui::PopStyleColor( 3 );
|
||||
}
|
||||
39
ImGuiToolkit.h
Normal file
39
ImGuiToolkit.h
Normal file
@@ -0,0 +1,39 @@
|
||||
#ifndef __IMGUI_TOOLKIT_H_
|
||||
#define __IMGUI_TOOLKIT_H_
|
||||
|
||||
#include <glib.h>
|
||||
#include <string>
|
||||
#include "rsc/fonts/IconsFontAwesome5.h"
|
||||
|
||||
namespace ImGuiToolkit
|
||||
{
|
||||
// Icons from resource icon.dds
|
||||
void Icon(int i, int j);
|
||||
bool ButtonIcon(int i, int j);
|
||||
void ShowIconsWindow(bool* p_open);
|
||||
void ToggleButton( const char* label, bool& toggle );
|
||||
void Bar(float value, float in, float out, float min, float max, const char* title, bool expand);
|
||||
bool TimelineSlider(const char* label, guint64 *time, guint64 begin, guint64 end, guint64 duration, guint64 step);
|
||||
|
||||
// fonts from ressources 'fonts/'
|
||||
typedef enum {
|
||||
FONT_DEFAULT =0,
|
||||
FONT_BOLD,
|
||||
FONT_ITALIC,
|
||||
FONT_MONO
|
||||
} font_style;
|
||||
void SetFont(font_style type, const std::string &ttf_font_name, int pointsize);
|
||||
void PushFont(font_style type);
|
||||
|
||||
// color of gui items
|
||||
typedef enum {
|
||||
ACCENT_BLUE =0,
|
||||
ACCENT_ORANGE,
|
||||
ACCENT_GREY
|
||||
} accent_color;
|
||||
void SetAccentColor(accent_color color);
|
||||
|
||||
std::string DateTime();
|
||||
};
|
||||
|
||||
#endif // __IMGUI_TOOLKIT_H_
|
||||
135
Log.cpp
Normal file
135
Log.cpp
Normal file
@@ -0,0 +1,135 @@
|
||||
#include "Log.h"
|
||||
|
||||
#include "imgui.h"
|
||||
#ifndef IMGUI_DEFINE_MATH_OPERATORS
|
||||
#define IMGUI_DEFINE_MATH_OPERATORS
|
||||
#endif
|
||||
#include "imgui_internal.h"
|
||||
|
||||
#include "ImGuiToolkit.h"
|
||||
|
||||
struct AppLog
|
||||
{
|
||||
ImGuiTextBuffer Buf;
|
||||
ImGuiTextFilter Filter;
|
||||
ImVector<int> LineOffsets;
|
||||
|
||||
AppLog()
|
||||
{
|
||||
Clear();
|
||||
}
|
||||
|
||||
void Clear()
|
||||
{
|
||||
Buf.clear();
|
||||
LineOffsets.clear();
|
||||
LineOffsets.push_back(0);
|
||||
}
|
||||
|
||||
void AddLog(const char* fmt, va_list args)
|
||||
{
|
||||
int old_size = Buf.size();
|
||||
Buf.appendfv(fmt, args);
|
||||
Buf.append("\n");
|
||||
|
||||
for (int new_size = Buf.size(); old_size < new_size; old_size++)
|
||||
if (Buf[old_size] == '\n')
|
||||
LineOffsets.push_back(old_size + 1);
|
||||
}
|
||||
|
||||
void Draw(const char* title, bool* p_open = NULL)
|
||||
{
|
||||
if (!ImGui::Begin(title, p_open))
|
||||
{
|
||||
ImGui::End();
|
||||
return;
|
||||
}
|
||||
|
||||
// window
|
||||
ImGui::SameLine(0, 0);
|
||||
bool clear = ImGui::Button( ICON_FA_BACKSPACE " Clear");
|
||||
ImGui::SameLine();
|
||||
bool copy = ImGui::Button( ICON_FA_COPY " Copy");
|
||||
ImGui::SameLine();
|
||||
Filter.Draw("Filter", -60.0f);
|
||||
|
||||
ImGui::Separator();
|
||||
ImGui::BeginChild("scrolling", ImVec2(0,0), false, ImGuiWindowFlags_HorizontalScrollbar);
|
||||
|
||||
if (clear)
|
||||
Clear();
|
||||
if (copy)
|
||||
ImGui::LogToClipboard();
|
||||
|
||||
ImGuiToolkit::PushFont(ImGuiToolkit::FONT_MONO);
|
||||
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(0, 0));
|
||||
const char* buf = Buf.begin();
|
||||
const char* buf_end = Buf.end();
|
||||
if (Filter.IsActive())
|
||||
{
|
||||
// In this example we don't use the clipper when Filter is enabled.
|
||||
// This is because we don't have a random access on the result on our filter.
|
||||
// A real application processing logs with ten of thousands of entries may want to store the result of search/filter.
|
||||
// especially if the filtering function is not trivial (e.g. reg-exp).
|
||||
for (int line_no = 0; line_no < LineOffsets.Size; line_no++)
|
||||
{
|
||||
const char* line_start = buf + LineOffsets[line_no];
|
||||
const char* line_end = (line_no + 1 < LineOffsets.Size) ? (buf + LineOffsets[line_no + 1] - 1) : buf_end;
|
||||
if (Filter.PassFilter(line_start, line_end))
|
||||
ImGui::TextUnformatted(line_start, line_end);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// The simplest and easy way to display the entire buffer:
|
||||
// ImGui::TextUnformatted(buf_begin, buf_end);
|
||||
// And it'll just work. TextUnformatted() has specialization for large blob of text and will fast-forward to skip non-visible lines.
|
||||
// Here we instead demonstrate using the clipper to only process lines that are within the visible area.
|
||||
// If you have tens of thousands of items and their processing cost is non-negligible, coarse clipping them on your side is recommended.
|
||||
// Using ImGuiListClipper requires A) random access into your data, and B) items all being the same height,
|
||||
// both of which we can handle since we an array pointing to the beginning of each line of text.
|
||||
// When using the filter (in the block of code above) we don't have random access into the data to display anymore, which is why we don't use the clipper.
|
||||
// Storing or skimming through the search result would make it possible (and would be recommended if you want to search through tens of thousands of entries)
|
||||
ImGuiListClipper clipper;
|
||||
clipper.Begin(LineOffsets.Size);
|
||||
while (clipper.Step())
|
||||
{
|
||||
for (int line_no = clipper.DisplayStart; line_no < clipper.DisplayEnd; line_no++)
|
||||
{
|
||||
const char* line_start = buf + LineOffsets[line_no];
|
||||
const char* line_end = (line_no + 1 < LineOffsets.Size) ? (buf + LineOffsets[line_no + 1] - 1) : buf_end;
|
||||
ImGui::TextUnformatted(line_start, line_end);
|
||||
}
|
||||
}
|
||||
clipper.End();
|
||||
}
|
||||
ImGui::PopStyleVar();
|
||||
|
||||
ImGui::PopFont();
|
||||
|
||||
// Auto scroll
|
||||
if (ImGui::GetScrollY() >= ImGui::GetScrollMaxY())
|
||||
ImGui::SetScrollHereY(1.0f);
|
||||
|
||||
ImGui::EndChild();
|
||||
ImGui::End();
|
||||
}
|
||||
};
|
||||
|
||||
static AppLog logs;
|
||||
|
||||
void Log::Info(const char* fmt, ...)
|
||||
{
|
||||
va_list args;
|
||||
va_start(args, fmt);
|
||||
logs.AddLog(fmt, args);
|
||||
va_end(args);
|
||||
}
|
||||
|
||||
void Log::ShowLogWindow(bool* p_open)
|
||||
{
|
||||
ImGui::SetNextWindowSize(ImVec2(500, 600), ImGuiCond_FirstUseEver);
|
||||
logs.Draw( ICON_FA_LIST_UL " Logs", p_open);
|
||||
}
|
||||
|
||||
12
Log.h
Normal file
12
Log.h
Normal file
@@ -0,0 +1,12 @@
|
||||
#ifndef __LOG_H_
|
||||
#define __LOG_H_
|
||||
|
||||
namespace Log
|
||||
{
|
||||
// log info
|
||||
void Info(const char* fmt, ...);
|
||||
// Draw logs
|
||||
void ShowLogWindow(bool* p_open = nullptr);
|
||||
};
|
||||
|
||||
#endif // __LOG_H_
|
||||
971
MediaPlayer.cpp
Normal file
971
MediaPlayer.cpp
Normal file
@@ -0,0 +1,971 @@
|
||||
#include "MediaPlayer.h"
|
||||
|
||||
// vmix
|
||||
#include "defines.h"
|
||||
#include "Log.h"
|
||||
#include "RenderingManager.h"
|
||||
#include "ResourceManager.h"
|
||||
#include "UserInterfaceManager.h"
|
||||
#include "GstToolkit.h"
|
||||
|
||||
// Desktop OpenGL function loader
|
||||
#include <glad/glad.h>
|
||||
|
||||
// GStreamer
|
||||
#include <gst/gl/gl.h>
|
||||
#include <gst/gstformat.h>
|
||||
#include <gst/pbutils/gstdiscoverer.h>
|
||||
#include <gst/app/gstappsink.h>
|
||||
|
||||
// opengl texture
|
||||
static GLuint tex_index_black = 0;
|
||||
static GstStaticCaps gl_render_caps = GST_STATIC_CAPS ("video/x-raw(memory:GLMemory),format=RGBA,texture-target=2D");
|
||||
static GstStaticCaps frame_render_caps = GST_STATIC_CAPS ("video/x-raw,format=RGB");
|
||||
|
||||
GLuint blackTexture()
|
||||
{
|
||||
// generate texture (once) & clear
|
||||
if (tex_index_black == 0) {
|
||||
glGenTextures(1, &tex_index_black);
|
||||
glBindTexture( GL_TEXTURE_2D, tex_index_black);
|
||||
unsigned char clearColor[3] = {0};
|
||||
// texture with one black pixel
|
||||
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, 1, 1, 0, GL_RGB, GL_UNSIGNED_BYTE, clearColor);
|
||||
}
|
||||
|
||||
return tex_index_black;
|
||||
}
|
||||
|
||||
MediaPlayer::MediaPlayer(string name) : id(name)
|
||||
{
|
||||
uri = "undefined";
|
||||
ready = false;
|
||||
seekable = false;
|
||||
isimage = false;
|
||||
pipeline = nullptr;
|
||||
discoverer = nullptr;
|
||||
|
||||
width = 640;
|
||||
height = 480;
|
||||
position = GST_CLOCK_TIME_NONE;
|
||||
duration = GST_CLOCK_TIME_NONE;
|
||||
start_position = GST_CLOCK_TIME_NONE;
|
||||
frame_duration = GST_CLOCK_TIME_NONE;
|
||||
desired_state = GST_STATE_PAUSED;
|
||||
rate = 1.0;
|
||||
framerate = 1.0;
|
||||
loop = LoopMode::LOOP_REWIND;
|
||||
need_loop = false;
|
||||
v_frame_is_full = false;
|
||||
|
||||
textureindex = 0;
|
||||
}
|
||||
|
||||
MediaPlayer::~MediaPlayer()
|
||||
{
|
||||
Close();
|
||||
// g_free(v_frame);
|
||||
}
|
||||
|
||||
void MediaPlayer::Bind()
|
||||
{
|
||||
glBindTexture(GL_TEXTURE_2D, Texture());
|
||||
}
|
||||
|
||||
guint MediaPlayer::Texture() const
|
||||
{
|
||||
if (textureindex == 0)
|
||||
return blackTexture();
|
||||
|
||||
return textureindex;
|
||||
}
|
||||
|
||||
void MediaPlayer::Open(string uri)
|
||||
{
|
||||
// set uri to open
|
||||
this->uri = uri;
|
||||
|
||||
// reset
|
||||
ready = false;
|
||||
|
||||
/* Instantiate the Discoverer */
|
||||
GError *err = NULL;
|
||||
discoverer = gst_discoverer_new (5 * GST_SECOND, &err);
|
||||
if (!discoverer) {
|
||||
Log::Info("Error creating discoverer instance: %s\n", err->message);
|
||||
g_clear_error (&err);
|
||||
return;
|
||||
}
|
||||
|
||||
// set callback for filling in information into this MediaPlayer
|
||||
g_signal_connect (discoverer, "discovered", G_CALLBACK (callback_discoverer_process), this);
|
||||
// set callback when finished discovering
|
||||
g_signal_connect (discoverer, "finished", G_CALLBACK (callback_discoverer_finished), this);
|
||||
|
||||
// start discoverer
|
||||
gst_discoverer_start(discoverer);
|
||||
// Add the request to process asynchronously the URI
|
||||
if (!gst_discoverer_discover_uri_async (discoverer, uri.c_str())) {
|
||||
UserInterface::Warning("Failed to start discovering URI '%s'\n", uri.c_str());
|
||||
g_object_unref (discoverer);
|
||||
discoverer = nullptr;
|
||||
}
|
||||
|
||||
// and wait for discoverer to finish...
|
||||
}
|
||||
|
||||
|
||||
void MediaPlayer::execute_open()
|
||||
{
|
||||
// build string describing pipeline
|
||||
string description = "uridecodebin uri=" + uri + " name=decoder ! videoconvert ! "
|
||||
"video/x-raw,format=RGB ! appsink name=sink";
|
||||
|
||||
// parse pipeline descriptor
|
||||
GError *error = NULL;
|
||||
pipeline = gst_parse_launch (description.c_str(), &error);
|
||||
if (error != NULL) {
|
||||
UserInterface::Warning("Could not construct pipeline %s:\n%s\n", description.c_str(), error->message);
|
||||
g_clear_error (&error);
|
||||
return;
|
||||
}
|
||||
g_object_set(G_OBJECT(pipeline), "name", id.c_str(), NULL);
|
||||
|
||||
// GstCaps *caps = gst_static_caps_get (&frame_render_caps);
|
||||
string capstring = "video/x-raw,format=RGB,width="+ std::to_string(width) + ",height=" + std::to_string(height);
|
||||
GstCaps *caps = gst_caps_from_string(capstring.c_str());
|
||||
if (!gst_video_info_from_caps (&v_frame_video_info, caps)) {
|
||||
UserInterface::Warning("%s: Could not configure MediaPlayer video frame info\n", gst_element_get_name(pipeline));
|
||||
return;
|
||||
}
|
||||
|
||||
// setup appsink
|
||||
GstElement *sink = gst_bin_get_by_name (GST_BIN (pipeline), "sink");
|
||||
if (sink) {
|
||||
|
||||
// set all properties
|
||||
g_object_set (sink, "emit-signals", TRUE, "sync", TRUE, "enable-last-sample", TRUE,
|
||||
"wait-on-eos", FALSE, "max-buffers", 1000, "caps", caps, NULL);
|
||||
|
||||
// connect callbacks
|
||||
g_signal_connect(G_OBJECT(sink), "new-sample", G_CALLBACK (callback_pull_sample_video), this);
|
||||
g_signal_connect(G_OBJECT(sink), "new-preroll", G_CALLBACK (callback_pull_sample_video), this);
|
||||
g_signal_connect(G_OBJECT(sink), "eos", G_CALLBACK (callback_end_of_video), this);
|
||||
|
||||
// Instruct appsink to drop old buffers when the maximum amount of queued buffers is reached.
|
||||
// here max-buffers set to 1000
|
||||
gst_app_sink_set_drop ( (GstAppSink*) sink, true);
|
||||
|
||||
// done with ref to sink
|
||||
gst_object_unref (sink);
|
||||
}
|
||||
else {
|
||||
UserInterface::Warning("%s: Could not configure MediaPlayer sink\n", gst_element_get_name(pipeline));
|
||||
return;
|
||||
}
|
||||
gst_caps_unref (caps);
|
||||
|
||||
// capture bus signals to force a unique opengl context for all GST elements
|
||||
//Rendering::LinkPipeline(GST_PIPELINE (pipeline));
|
||||
|
||||
// set to desired state (PLAY or PAUSE)
|
||||
GstStateChangeReturn ret = gst_element_set_state (pipeline, desired_state);
|
||||
if (ret == GST_STATE_CHANGE_FAILURE) {
|
||||
UserInterface::Warning("%s: Failed to open media %s \n%s\n", gst_element_get_name(pipeline), uri.c_str(), discoverer_message.str().c_str());
|
||||
}
|
||||
else {
|
||||
Log::Info("%s: Media Player openned %s\n", gst_element_get_name(pipeline), uri.c_str());
|
||||
|
||||
// all good
|
||||
ready = true;
|
||||
}
|
||||
|
||||
discoverer_message.clear();
|
||||
}
|
||||
|
||||
bool MediaPlayer::isOpen() const
|
||||
{
|
||||
return ready;
|
||||
}
|
||||
|
||||
void MediaPlayer::Close()
|
||||
{
|
||||
// stop discovering stream
|
||||
if (discoverer != nullptr) {
|
||||
gst_discoverer_stop (discoverer);
|
||||
g_object_unref (discoverer);
|
||||
discoverer = nullptr;
|
||||
}
|
||||
|
||||
if (!ready)
|
||||
return;
|
||||
|
||||
/* clean up GST */
|
||||
if (pipeline != nullptr) {
|
||||
gst_element_set_state (pipeline, GST_STATE_NULL);
|
||||
gst_object_unref (pipeline);
|
||||
pipeline = nullptr;
|
||||
}
|
||||
|
||||
// nothing to display
|
||||
textureindex = blackTexture();
|
||||
|
||||
// un-ready the media player
|
||||
ready = false;
|
||||
}
|
||||
|
||||
|
||||
GstClockTime MediaPlayer::Duration()
|
||||
{
|
||||
if (duration == GST_CLOCK_TIME_NONE && pipeline != nullptr) {
|
||||
gint64 d = GST_CLOCK_TIME_NONE;
|
||||
if ( gst_element_query_duration(pipeline, GST_FORMAT_TIME, &d) )
|
||||
duration = d;
|
||||
}
|
||||
|
||||
return duration;
|
||||
}
|
||||
|
||||
GstClockTime MediaPlayer::FrameDuration()
|
||||
{
|
||||
return frame_duration;
|
||||
}
|
||||
|
||||
guint MediaPlayer::Width() const
|
||||
{
|
||||
return width;
|
||||
}
|
||||
|
||||
guint MediaPlayer::Height() const
|
||||
{
|
||||
return height;
|
||||
}
|
||||
|
||||
float MediaPlayer::AspectRatio() const
|
||||
{
|
||||
return static_cast<float>(width) / static_cast<float>(height);
|
||||
}
|
||||
|
||||
GstClockTime MediaPlayer::Position()
|
||||
{
|
||||
GstClockTime pos = position;
|
||||
|
||||
if (pos == GST_CLOCK_TIME_NONE && pipeline != nullptr) {
|
||||
gint64 p = GST_CLOCK_TIME_NONE;
|
||||
if ( gst_element_query_position (pipeline, GST_FORMAT_TIME, &p) )
|
||||
pos = p;
|
||||
}
|
||||
|
||||
return pos - start_position;
|
||||
}
|
||||
|
||||
void MediaPlayer::Play(bool on)
|
||||
{
|
||||
if (isimage)
|
||||
return;
|
||||
|
||||
// request state
|
||||
desired_state = on ? GST_STATE_PLAYING : GST_STATE_PAUSED;
|
||||
|
||||
// if not ready yet, the requested state will be handled later
|
||||
if ( pipeline == nullptr )
|
||||
return;
|
||||
|
||||
// requesting to play, but stopped at end of stream : rewind first !
|
||||
if ( desired_state == GST_STATE_PLAYING) {
|
||||
if ( ( rate>0.0 ? duration - Position() : Position() ) < 2 * frame_duration )
|
||||
Rewind();
|
||||
}
|
||||
|
||||
// all ready, apply state change immediately
|
||||
GstStateChangeReturn ret = gst_element_set_state (pipeline, desired_state);
|
||||
if (ret == GST_STATE_CHANGE_FAILURE) {
|
||||
UserInterface::Warning("Failed to start up Media %s\n", gst_element_get_name(pipeline));
|
||||
}
|
||||
else if (on)
|
||||
Log::Info("Start Media %s\n", gst_element_get_name(pipeline));
|
||||
else
|
||||
Log::Info("Stop Media %s\n", gst_element_get_name(pipeline));
|
||||
}
|
||||
|
||||
bool MediaPlayer::isPlaying() const
|
||||
{
|
||||
// image cannot play
|
||||
if (isimage)
|
||||
return false;
|
||||
|
||||
// if not ready yet, answer with requested state
|
||||
if ( pipeline == nullptr )
|
||||
return desired_state == GST_STATE_PLAYING;
|
||||
|
||||
// if ready, answer with actual state
|
||||
GstState state;
|
||||
gst_element_get_state (pipeline, &state, NULL, GST_CLOCK_TIME_NONE);
|
||||
return state == GST_STATE_PLAYING;
|
||||
|
||||
// return desired_state == GST_STATE_PLAYING;
|
||||
}
|
||||
|
||||
|
||||
MediaPlayer::LoopMode MediaPlayer::Loop() const
|
||||
{
|
||||
return loop;
|
||||
}
|
||||
|
||||
void MediaPlayer::setLoop(MediaPlayer::LoopMode mode)
|
||||
{
|
||||
loop = mode;
|
||||
}
|
||||
|
||||
void MediaPlayer::Rewind()
|
||||
{
|
||||
if (!seekable)
|
||||
return;
|
||||
|
||||
if (rate > 0.0)
|
||||
// playing forward, loop to begin
|
||||
execute_seek_command(0);
|
||||
else
|
||||
// playing backward, loop to end
|
||||
execute_seek_command(duration);
|
||||
}
|
||||
|
||||
|
||||
void MediaPlayer::SeekNextFrame()
|
||||
{
|
||||
// useful only when Paused
|
||||
if (isPlaying())
|
||||
return;
|
||||
|
||||
if ( loop != LOOP_NONE) {
|
||||
// eventually loop if mode allows
|
||||
if ( ( rate>0.0 ? duration - Position() : Position() ) < 2 * frame_duration )
|
||||
need_loop = true;
|
||||
}
|
||||
|
||||
// step
|
||||
gst_element_send_event (pipeline, gst_event_new_step (GST_FORMAT_BUFFERS, 1, ABS(rate), TRUE, FALSE));
|
||||
|
||||
}
|
||||
|
||||
void MediaPlayer::SeekTo(GstClockTime pos)
|
||||
{
|
||||
if (!seekable)
|
||||
return;
|
||||
|
||||
GstClockTime target = CLAMP(pos, 0, duration);
|
||||
execute_seek_command(target);
|
||||
}
|
||||
|
||||
void MediaPlayer::FastForward()
|
||||
{
|
||||
if (!seekable)
|
||||
return;
|
||||
|
||||
double step = SIGN(rate) * 0.01 * static_cast<double>(duration);
|
||||
GstClockTime target = Position() + static_cast<GstClockTime>(step);
|
||||
|
||||
// manage loop
|
||||
if ( target > duration ) {
|
||||
if (loop == LOOP_NONE)
|
||||
target = duration;
|
||||
else
|
||||
target = target - duration;
|
||||
}
|
||||
|
||||
SeekTo(target);
|
||||
}
|
||||
|
||||
|
||||
void MediaPlayer::Update()
|
||||
{
|
||||
// discard
|
||||
if (!ready) return;
|
||||
|
||||
// done discovering stream
|
||||
if (discoverer != nullptr) {
|
||||
gst_discoverer_stop (discoverer);
|
||||
g_object_unref (discoverer);
|
||||
discoverer = nullptr;
|
||||
}
|
||||
|
||||
// apply texture
|
||||
if (v_frame_is_full) {
|
||||
if (textureindex==0) {
|
||||
glActiveTexture(GL_TEXTURE0);
|
||||
glGenTextures(1, &textureindex);
|
||||
glBindTexture(GL_TEXTURE_2D, textureindex);
|
||||
glPixelStorei(GL_UNPACK_ALIGNMENT, 3);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
|
||||
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height,
|
||||
0, GL_RGB, GL_UNSIGNED_BYTE, v_frame.data[0]);
|
||||
}
|
||||
else
|
||||
{
|
||||
glBindTexture(GL_TEXTURE_2D, textureindex);
|
||||
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, width, height,
|
||||
GL_RGB, GL_UNSIGNED_BYTE, v_frame.data[0]);
|
||||
}
|
||||
|
||||
// sync with callback_pull_last_sample_video
|
||||
v_frame_is_full = false;
|
||||
}
|
||||
|
||||
// manage loop mode
|
||||
if (need_loop && !isimage) {
|
||||
if (loop == LOOP_NONE)
|
||||
Play(false);
|
||||
else
|
||||
execute_loop_command();
|
||||
|
||||
need_loop = false;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void MediaPlayer::execute_loop_command()
|
||||
{
|
||||
if ( pipeline == nullptr) return;
|
||||
|
||||
if (loop==LOOP_REWIND) {
|
||||
Rewind();
|
||||
}
|
||||
else if (loop==LOOP_BIDIRECTIONAL) {
|
||||
rate *= - 1.f;
|
||||
execute_seek_command();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void MediaPlayer::execute_seek_command(GstClockTime target)
|
||||
{
|
||||
if ( pipeline == nullptr || !seekable)
|
||||
return;
|
||||
|
||||
GstEvent *seek_event = nullptr;
|
||||
|
||||
// seek position : default to target
|
||||
GstClockTime seek_pos = target;
|
||||
// seek speed rate
|
||||
gdouble seek_rate = rate;
|
||||
// seek with flush (always)
|
||||
int seek_flags = GST_SEEK_FLAG_FLUSH;
|
||||
|
||||
// no target given
|
||||
if (target == GST_CLOCK_TIME_NONE)
|
||||
// create seek event with current position (rate changed ?)
|
||||
seek_pos = position;
|
||||
// target is given but useless
|
||||
else if (target == Position())
|
||||
// ignore request
|
||||
return;
|
||||
|
||||
// seek with trick mode if fast speed
|
||||
if ( ABS(seek_rate) > 2.0 )
|
||||
seek_flags |= GST_SEEK_FLAG_TRICKMODE | GST_SEEK_FLAG_TRICKMODE_NO_AUDIO;
|
||||
|
||||
// create seek event depending on direction
|
||||
if (seek_rate > 0) {
|
||||
seek_event = gst_event_new_seek (seek_rate, GST_FORMAT_TIME, (GstSeekFlags) seek_flags,
|
||||
GST_SEEK_TYPE_SET, seek_pos, GST_SEEK_TYPE_END, 0);
|
||||
} else {
|
||||
seek_event = gst_event_new_seek (seek_rate, GST_FORMAT_TIME, (GstSeekFlags) seek_flags,
|
||||
GST_SEEK_TYPE_SET, 0, GST_SEEK_TYPE_SET, seek_pos);
|
||||
}
|
||||
|
||||
/* Send the event */
|
||||
if (seek_event && !gst_element_send_event(pipeline, seek_event) )
|
||||
Log::Info("Seek failed in Media %s\n", gst_element_get_name(pipeline));
|
||||
else
|
||||
{
|
||||
Log::Info("Seek Media %s %ld %f\n", gst_element_get_name(pipeline), seek_pos, seek_rate);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
void MediaPlayer::SetPlaySpeed(double s)
|
||||
{
|
||||
if (isimage)
|
||||
return;
|
||||
|
||||
// bound to interval [-MAX_PLAY_SPEED MAX_PLAY_SPEED]
|
||||
rate = CLAMP(s, -MAX_PLAY_SPEED, MAX_PLAY_SPEED);
|
||||
// skip interval [-MIN_PLAY_SPEED MIN_PLAY_SPEED]
|
||||
if (ABS(rate) < MIN_PLAY_SPEED)
|
||||
rate = SIGN(rate) * MIN_PLAY_SPEED;
|
||||
|
||||
// apply with seek
|
||||
execute_seek_command();
|
||||
}
|
||||
|
||||
double MediaPlayer::PlaySpeed() const
|
||||
{
|
||||
return rate;
|
||||
}
|
||||
|
||||
|
||||
double MediaPlayer::FrameRate() const
|
||||
{
|
||||
return framerate;
|
||||
}
|
||||
|
||||
double MediaPlayer::UpdateFrameRate() const
|
||||
{
|
||||
return timecount.framerate();
|
||||
}
|
||||
|
||||
// CALLBACKS
|
||||
|
||||
bool MediaPlayer::fill_v_frame(GstBuffer *buf)
|
||||
{
|
||||
// always empty frame before filling it again
|
||||
if (v_frame.buffer)
|
||||
gst_video_frame_unmap(&v_frame);
|
||||
|
||||
// get the frame from buffer
|
||||
if ( !gst_video_frame_map (&v_frame, &v_frame_video_info, buf, GST_MAP_READ ) ) {
|
||||
Log::Info("Failed to map the video buffer");
|
||||
return false;
|
||||
}
|
||||
|
||||
// validate frame format
|
||||
if( GST_VIDEO_INFO_IS_RGB(&(v_frame).info) && GST_VIDEO_INFO_N_PLANES(&(v_frame).info) == 1) {
|
||||
|
||||
// validate time
|
||||
if (position != buf->pts) {
|
||||
|
||||
// got a new RGB frame !
|
||||
v_frame_is_full = true;
|
||||
|
||||
// get presentation time stamp
|
||||
position = buf->pts;
|
||||
|
||||
// set start position (i.e. pts of first frame we got)
|
||||
if (start_position == GST_CLOCK_TIME_NONE)
|
||||
start_position = position;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
GstFlowReturn MediaPlayer::callback_get_last_sample_video (GstElement *bin, MediaPlayer *m)
|
||||
{
|
||||
GstFlowReturn ret = GST_FLOW_OK;
|
||||
|
||||
if (m && !m->v_frame_is_full) {
|
||||
// get last sample (non blocking)
|
||||
GstSample *sample = nullptr;
|
||||
g_object_get (bin, "last-sample", &sample, NULL);
|
||||
|
||||
if (sample != nullptr) {
|
||||
|
||||
GstBuffer *buf;
|
||||
buf = gst_buffer_ref ( gst_sample_get_buffer (sample) );
|
||||
gst_sample_unref (sample);
|
||||
|
||||
if ( !m->fill_v_frame(buf) )
|
||||
ret = GST_FLOW_ERROR;
|
||||
gst_buffer_unref (buf);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
GstFlowReturn MediaPlayer::callback_pull_sample_video (GstElement *bin, MediaPlayer *m)
|
||||
{
|
||||
GstFlowReturn ret = GST_FLOW_OK;
|
||||
|
||||
if (m && !m->v_frame_is_full) {
|
||||
|
||||
// get last sample (non blocking)
|
||||
GstSample *sample = nullptr;
|
||||
g_object_get (bin, "last-sample", &sample, NULL);
|
||||
|
||||
// if got a valid sample
|
||||
if (sample != nullptr) {
|
||||
|
||||
// get buffer from sample
|
||||
GstBuffer *buf = gst_buffer_ref ( gst_sample_get_buffer (sample) );
|
||||
|
||||
// fill frame from buffer
|
||||
if ( !m->fill_v_frame(buf) )
|
||||
ret = GST_FLOW_ERROR;
|
||||
// free buffer
|
||||
gst_buffer_unref (buf);
|
||||
}
|
||||
else
|
||||
ret = GST_FLOW_FLUSHING;
|
||||
|
||||
// cleanup stack of samples (non blocking)
|
||||
// NB : overkill as gst_app_sink_set_drop is set to TRUE, but better be safe than leak memory...
|
||||
while (sample != nullptr) {
|
||||
gst_sample_unref (sample);
|
||||
sample = gst_app_sink_try_pull_sample( (GstAppSink * ) bin, 0 );
|
||||
}
|
||||
|
||||
// keep update time (i.e. actual FPS of update)
|
||||
m->timecount.tic();
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
void MediaPlayer::callback_end_of_video (GstElement *bin, MediaPlayer *m)
|
||||
{
|
||||
if (m) {
|
||||
// reached end of stream (eos) : might need to loop !
|
||||
m->need_loop = true;
|
||||
}
|
||||
}
|
||||
|
||||
void MediaPlayer::callback_discoverer_process (GstDiscoverer *discoverer, GstDiscovererInfo *info, GError *err, MediaPlayer *m)
|
||||
{
|
||||
// handle general errors
|
||||
const gchar *uri = gst_discoverer_info_get_uri (info);
|
||||
GstDiscovererResult result = gst_discoverer_info_get_result (info);
|
||||
switch (result) {
|
||||
case GST_DISCOVERER_URI_INVALID:
|
||||
// g_print ("Invalid URI '%s'\n", uri);
|
||||
m->discoverer_message << "Invalid URI: " << uri;
|
||||
break;
|
||||
case GST_DISCOVERER_ERROR:
|
||||
// g_print ("Discoverer error: %s\n", err->message);
|
||||
m->discoverer_message << "Error: " << err->message;
|
||||
break;
|
||||
case GST_DISCOVERER_TIMEOUT:
|
||||
m->discoverer_message << "Time out";
|
||||
break;
|
||||
case GST_DISCOVERER_BUSY:
|
||||
m->discoverer_message << "Busy";
|
||||
break;
|
||||
case GST_DISCOVERER_MISSING_PLUGINS:
|
||||
{
|
||||
const GstStructure *s = gst_discoverer_info_get_misc (info);
|
||||
gchar *str = gst_structure_to_string (s);
|
||||
m->discoverer_message << "Missing plugin " << str;
|
||||
g_free (str);
|
||||
}
|
||||
break;
|
||||
case GST_DISCOVERER_OK:
|
||||
break;
|
||||
}
|
||||
// no error, handle information found
|
||||
if ( result == GST_DISCOVERER_OK && m) {
|
||||
|
||||
// look for video stream at that uri
|
||||
bool foundvideostream = false;
|
||||
GList *streams = gst_discoverer_info_get_video_streams(info);
|
||||
GList *tmp;
|
||||
for (tmp = streams; tmp && !foundvideostream; tmp = tmp->next ) {
|
||||
GstDiscovererStreamInfo *tmpinf = (GstDiscovererStreamInfo *) tmp->data;
|
||||
if ( GST_IS_DISCOVERER_VIDEO_INFO(tmpinf) )
|
||||
{
|
||||
GstDiscovererVideoInfo* vinfo = GST_DISCOVERER_VIDEO_INFO(tmpinf);
|
||||
m->width = gst_discoverer_video_info_get_width(vinfo);
|
||||
m->height = gst_discoverer_video_info_get_height(vinfo);
|
||||
m->isimage = gst_discoverer_video_info_is_image(vinfo);
|
||||
if ( !m->isimage ) {
|
||||
m->duration = gst_discoverer_info_get_duration (info);
|
||||
m->seekable = gst_discoverer_info_get_seekable (info);
|
||||
guint frn = gst_discoverer_video_info_get_framerate_num(vinfo);
|
||||
guint frd = gst_discoverer_video_info_get_framerate_denom(vinfo);
|
||||
m->framerate = static_cast<double>(frn) / static_cast<double>(frd);
|
||||
m->frame_duration = (GST_SECOND * static_cast<guint64>(frd)) / (static_cast<guint64>(frn));
|
||||
}
|
||||
foundvideostream = true;
|
||||
}
|
||||
}
|
||||
gst_discoverer_stream_info_list_free (streams);
|
||||
|
||||
if (!foundvideostream) {
|
||||
m->discoverer_message << "No video stream.";
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
void MediaPlayer::callback_discoverer_finished(GstDiscoverer *discoverer, MediaPlayer *m)
|
||||
{
|
||||
// finished the discoverer : finalize open status
|
||||
if (m)
|
||||
m->execute_open();
|
||||
}
|
||||
|
||||
TimeCounter::TimeCounter() {
|
||||
current_time = gst_util_get_timestamp ();
|
||||
last_time = gst_util_get_timestamp();
|
||||
nbFrames = 0;
|
||||
fps = 0.f;
|
||||
}
|
||||
|
||||
void TimeCounter::tic ()
|
||||
{
|
||||
current_time = gst_util_get_timestamp ();
|
||||
nbFrames++ ;
|
||||
if ((current_time - last_time) >= GST_SECOND)
|
||||
{
|
||||
last_time = current_time;
|
||||
fps = 0.2f * fps + 0.8f * static_cast<float>(nbFrames);
|
||||
nbFrames = 0;
|
||||
}
|
||||
}
|
||||
|
||||
float TimeCounter::framerate() const
|
||||
{
|
||||
return fps;
|
||||
}
|
||||
|
||||
int TimeCounter::framecount() const
|
||||
{
|
||||
return nbFrames;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
// /* TESTING */
|
||||
|
||||
|
||||
// static void cb_new_pad (GstElement* decodebin, GstPad* pad, GstElement* target)
|
||||
// {
|
||||
// GstPad* target_pad = gst_element_get_static_pad (target, "sink");
|
||||
// if (!target_pad)
|
||||
// g_warning ("target has no sink\n");
|
||||
|
||||
// //only link once
|
||||
// if (GST_PAD_IS_LINKED (target_pad))
|
||||
// {
|
||||
// gst_object_unref (target_pad);
|
||||
// g_warning ("target not free\n");
|
||||
// return;
|
||||
// }
|
||||
|
||||
// GstCaps* caps = gst_pad_get_current_caps (pad);
|
||||
// GstStructure* str = gst_caps_get_structure (caps, 0);
|
||||
|
||||
// if (!g_strrstr (gst_structure_get_name (str), "video"))
|
||||
// {
|
||||
// gst_caps_unref (caps);
|
||||
// gst_object_unref (target_pad);
|
||||
// g_warning ("decodebin not a video %s\n", gst_structure_get_name (str));
|
||||
// return;
|
||||
// }
|
||||
// gst_caps_unref (caps);
|
||||
|
||||
// GstPadLinkReturn ret = gst_pad_link (pad, target_pad);
|
||||
// if (ret != GST_PAD_LINK_OK)
|
||||
// g_warning ("Failed to link decodebin %s with target %s ! %d\n", gst_pad_get_name(pad), gst_pad_get_name(target_pad), ret);
|
||||
|
||||
// }
|
||||
|
||||
// GstElement* videosrc = gst_element_factory_make ("filesrc", "filesrc0");
|
||||
// GstElement* decodebin = gst_element_factory_make ("decodebin", "decodebin0");
|
||||
// // GstElement* convertbin = gst_element_factory_make ("videoconvert", "converterbin0");
|
||||
// GstElement* convertbin = gst_element_factory_make ("glupload", "converterbin0");
|
||||
// // GstElement* glimagesink = gst_element_factory_make ("glimagesink", "glimagesink0");
|
||||
// GstElement* glimagesink = gst_element_factory_make ("glimagesinkelement", "glimagesink0");
|
||||
// // GstElement* glimagesink = gst_element_factory_make ("fakesink", "glimagesink0");
|
||||
|
||||
// if (!videosrc || !decodebin || !convertbin || !glimagesink)
|
||||
// {
|
||||
// UserInterface::Error ("A Gstreamer element could not be found \n");
|
||||
// return 1;
|
||||
// }
|
||||
// /* configure elements */
|
||||
// g_object_set(G_OBJECT(videosrc), "num-buffers", 800, NULL);
|
||||
// g_object_set(G_OBJECT(videosrc), "location", video_location.c_str(), NULL);
|
||||
// g_signal_connect(G_OBJECT(decodebin), "drained", G_CALLBACK (endVideoCallback), NULL);
|
||||
|
||||
// // glimagesink
|
||||
// // g_signal_connect(G_OBJECT(glimagesink), "client-draw", G_CALLBACK (drawCallback), NULL);
|
||||
|
||||
// // glimagesinkelement
|
||||
// // g_signal_connect(G_OBJECT(glimagesink), "client-reshape", G_CALLBACK (scaleTextureCallback), NULL);
|
||||
// g_signal_connect(G_OBJECT(glimagesink), "client-draw", G_CALLBACK (textureCallback), NULL);
|
||||
// // // g_object_set(G_OBJECT(glimagesink), "show-preroll-frame", TRUE, NULL);
|
||||
// g_object_set(G_OBJECT(glimagesink), "sync", TRUE, NULL);
|
||||
// g_object_set(G_OBJECT(glimagesink), "handle-events", FALSE, NULL);
|
||||
|
||||
// // fakesink
|
||||
// // g_signal_connect(G_OBJECT(glimagesink), "handoff", G_CALLBACK (fakesinkCallback), NULL);
|
||||
// // g_object_set(G_OBJECT(glimagesink), "signal-handoffs", TRUE, NULL);
|
||||
// // g_object_set(G_OBJECT(glimagesink), "sync", TRUE, NULL);
|
||||
|
||||
// /* add elements */
|
||||
// gst_bin_add_many (GST_BIN (pipeline), videosrc, decodebin, convertbin, glimagesink, NULL);
|
||||
|
||||
// /* link elements */
|
||||
// if (!gst_element_link (videosrc, decodebin) )
|
||||
// // if (!gst_element_link_pads (videosrc, "src", decodebin, "sink") )
|
||||
// {
|
||||
// g_warning ("Failed to link videosrc to decodebin !\n");
|
||||
// return -1;
|
||||
// }
|
||||
|
||||
// g_signal_connect (decodebin, "pad-added", G_CALLBACK (cb_new_pad), convertbin);
|
||||
|
||||
// if(!gst_element_link (convertbin, glimagesink))
|
||||
// {
|
||||
// g_warning("Failed to link convertbin to glimagesink!\n") ;
|
||||
// return -1 ;
|
||||
// }
|
||||
|
||||
|
||||
// static gboolean drawTextureCallback (GstElement *object, guint texture, guint width, guint height, GstElement *pipeline)
|
||||
// {
|
||||
// //printStreamPosition(pipeline);
|
||||
|
||||
// texture_gst = texture;
|
||||
// //std::cerr << "Texture " << width << " x " << height << std::endl;
|
||||
|
||||
// return TRUE;
|
||||
// }
|
||||
|
||||
// static void fakesinkCallback (GstElement *bin, GstBuffer buffer, GstPad pad, GstElement *pipeline)
|
||||
// {
|
||||
// static GstClockTime current_time;
|
||||
// static GstClockTime last_time = gst_util_get_timestamp();
|
||||
// static gint nbFrames = 0;
|
||||
|
||||
// current_time = gst_util_get_timestamp ();
|
||||
// nbFrames++ ;
|
||||
|
||||
// if ((current_time - last_time) >= GST_SECOND)
|
||||
// {
|
||||
// //std::cerr << nbFrames << " frame / s" << std::endl;
|
||||
|
||||
// // GstElement *pipeline = (GstElement*)data;
|
||||
|
||||
// last_time = current_time;
|
||||
// nbFrames = 0;
|
||||
|
||||
// // if (!gst_element_seek (pipeline, 1.0, GST_FORMAT_TIME, GST_SEEK_FLAG_FLUSH,
|
||||
// // GST_SEEK_TYPE_SET, 0,
|
||||
// // GST_SEEK_TYPE_NONE, GST_CLOCK_TIME_NONE)) {
|
||||
// // g_print ("Seek failed!\n");
|
||||
// // }
|
||||
|
||||
// }
|
||||
|
||||
// }
|
||||
|
||||
// static void endVideoCallback (GstElement *bin, GstElement *pipeline)
|
||||
// {
|
||||
// g_warning ("end of stream %s\n", gst_element_get_name(pipeline));
|
||||
|
||||
// printStreamPosition(pipeline);
|
||||
|
||||
// }
|
||||
|
||||
// video
|
||||
|
||||
// gchar *descr;
|
||||
// // descr = g_strdup_printf ("videotestsrc pattern=ball ! video/x-raw,format=RGBA ! glimagesink");
|
||||
// // descr = g_strdup_printf ("videotestsrc ! glupload ! glfilterapp name=capture ! fakesink name=sink");
|
||||
// descr = g_strdup_printf ("uridecodebin uri=file:///home/bhbn/Videos/glmixervideo180324111104.mp4 name=decoder ! videoconvert ! "
|
||||
// "video/x-raw,format=RGBA ! glupload ! glfilterapp name=capture ! fakesink name=sink");
|
||||
// g_print ("pipeline: %s\n", descr);
|
||||
|
||||
// GError *error = NULL;
|
||||
// GstElement *pipeline = gst_parse_launch (descr, &error);
|
||||
// if (error != NULL) {
|
||||
// g_print ("could not construct pipeline: %s\n", error->message);
|
||||
// g_clear_error (&error);
|
||||
// exit (-1);
|
||||
// }
|
||||
// g_object_set(G_OBJECT(pipeline), "name", "vmixpipeline", NULL);
|
||||
|
||||
// GstElement *capture = gst_bin_get_by_name (GST_BIN (pipeline), "capture");
|
||||
// if (capture)
|
||||
// g_signal_connect(G_OBJECT(capture), "client-draw", G_CALLBACK (drawTextureCallback), pipeline);
|
||||
// GstElement *sink = gst_bin_get_by_name (GST_BIN (pipeline), "sink");
|
||||
// if (sink) {
|
||||
// g_signal_connect(G_OBJECT(sink), "handoff", G_CALLBACK (fakesinkCallback), pipeline);
|
||||
// g_object_set(G_OBJECT(sink), "signal-handoffs", TRUE, NULL);
|
||||
// g_object_set(G_OBJECT(sink), "sync", TRUE, NULL);
|
||||
// }
|
||||
// GstElement *decoder = gst_bin_get_by_name (GST_BIN (pipeline), "decoder");
|
||||
// if (decoder)
|
||||
// g_signal_connect(G_OBJECT(decoder), "drained", G_CALLBACK (endVideoCallback), pipeline);
|
||||
// // g_signal_connect(G_OBJECT(decoder), "about-to-finish", G_CALLBACK (endVideoCallback), pipeline);
|
||||
|
||||
// // g_timeout_add (200, (GSourceFunc) printStreamPosition, pipeline);
|
||||
|
||||
// // /* WORKING PIPELINE PURE OGL */
|
||||
// // GstElement* pipeline = gst_pipeline_new ("pipeline");
|
||||
// // GstElement *src = gst_element_factory_make ("gltestsrc", NULL);
|
||||
|
||||
// // // GstElement *sink = gst_element_factory_make ("glimagesinkelement", NULL);
|
||||
// // // g_signal_connect(G_OBJECT(sink), "client-draw", G_CALLBACK (drawCallback), NULL);
|
||||
// // // g_object_set(G_OBJECT(sink), "show-preroll-frame", FALSE, NULL);
|
||||
// // // g_object_set(G_OBJECT(sink), "handle-events", FALSE, NULL);
|
||||
|
||||
// // GstElement *capture = gst_element_factory_make ("glfilterapp", NULL);
|
||||
// // g_signal_connect(G_OBJECT(capture), "client-draw", G_CALLBACK (drawTextureCallback), NULL);
|
||||
// // GstElement* sink = gst_element_factory_make ("fakesink", NULL);
|
||||
// // g_signal_connect(G_OBJECT(sink), "handoff", G_CALLBACK (fakesinkCallback), NULL);
|
||||
// // g_object_set(G_OBJECT(sink), "signal-handoffs", TRUE, NULL);
|
||||
// // g_object_set(G_OBJECT(sink), "sync", TRUE, NULL);
|
||||
|
||||
// // gst_bin_add_many (GST_BIN (pipeline), src, capture, sink, NULL);
|
||||
// // gst_element_link_many (src, capture, sink, NULL);
|
||||
|
||||
|
||||
// // capture bus signals to force a unique opengl context for all GST elements
|
||||
// Rendering::LinkPipeline(GST_PIPELINE (pipeline));
|
||||
|
||||
// /* run */
|
||||
// GstStateChangeReturn ret = gst_element_set_state (pipeline, GST_STATE_PLAYING);
|
||||
// if (ret == GST_STATE_CHANGE_FAILURE)
|
||||
// {
|
||||
// g_warning ("Failed to start up pipeline!\n");
|
||||
|
||||
// return -1;
|
||||
// }
|
||||
|
||||
// string description = "uridecodebin uri=" + uri + " name=decoder ! videoconvert ! "
|
||||
// "video/x-raw,format=RGBA ! glupload ! glfilterapp name=capture ! appsink name=sink";
|
||||
|
||||
// GError *error = NULL;
|
||||
// pipeline = gst_parse_launch (description.c_str(), &error);
|
||||
// if (error != NULL) {
|
||||
// ImGuiToolkit::Log("Could not construct pipeline %s:\n%s\n", description.c_str(), error->message);
|
||||
// g_clear_error (&error);
|
||||
// return false;
|
||||
// }
|
||||
// g_object_set(G_OBJECT(pipeline), "name", id.c_str(), NULL);
|
||||
|
||||
// GstElement *capture = gst_bin_get_by_name (GST_BIN (pipeline), "capture");
|
||||
// if (capture) {
|
||||
// g_signal_connect(G_OBJECT(capture), "client-draw", G_CALLBACK (textureCallback), this);
|
||||
// }
|
||||
|
||||
// GstElement *sink = gst_bin_get_by_name (GST_BIN (pipeline), "sink");
|
||||
// if (sink) {
|
||||
// GstCaps *caps = gst_static_caps_get (&render_caps);
|
||||
|
||||
// g_object_set (sink, "emit-signals", TRUE, "enable-last-sample", TRUE, "sync", TRUE, "wait-on-eos", FALSE, "caps", caps, NULL);
|
||||
// g_signal_connect(G_OBJECT(sink), "new-sample", G_CALLBACK (sampleCallback), this);
|
||||
// g_signal_connect(G_OBJECT(sink), "new-preroll", G_CALLBACK (sampleCallback), this);
|
||||
// g_signal_connect(G_OBJECT(sink), "eos", G_CALLBACK (endMediaCallback), this);
|
||||
|
||||
// gst_object_unref (sink);
|
||||
// gst_caps_unref (caps);
|
||||
// }
|
||||
|
||||
// // capture bus signals to force a unique opengl context for all GST elements
|
||||
// Rendering::LinkPipeline(GST_PIPELINE (pipeline));
|
||||
|
||||
// /* set to PAUSED to make the first frame arrive in the sink */
|
||||
// GstStateChangeReturn ret = gst_element_set_state (pipeline, GST_STATE_PAUSED);
|
||||
// if (ret == GST_STATE_CHANGE_FAILURE) {
|
||||
// ImGuiToolkit::Log("%s: Failed to open media %s\n", gst_element_get_name(pipeline), uri.c_str());
|
||||
// return false;
|
||||
// }
|
||||
187
MediaPlayer.h
Normal file
187
MediaPlayer.h
Normal file
@@ -0,0 +1,187 @@
|
||||
#ifndef __GST_MEDIA_PLAYER_H_
|
||||
#define __GST_MEDIA_PLAYER_H_
|
||||
|
||||
#include <string>
|
||||
#include <atomic>
|
||||
#include <sstream>
|
||||
using namespace std;
|
||||
|
||||
#include <gst/gst.h>
|
||||
#include <gst/gl/gl.h>
|
||||
#include <gst/pbutils/pbutils.h>
|
||||
|
||||
#define MAX_PLAY_SPEED 20.0
|
||||
#define MIN_PLAY_SPEED 0.1
|
||||
|
||||
struct TimeCounter {
|
||||
|
||||
GstClockTime current_time;
|
||||
GstClockTime last_time;
|
||||
int nbFrames;
|
||||
float fps;
|
||||
public:
|
||||
TimeCounter();
|
||||
void tic();
|
||||
float framerate() const;
|
||||
int framecount() const;
|
||||
};
|
||||
|
||||
class MediaPlayer {
|
||||
|
||||
public:
|
||||
|
||||
/**
|
||||
* Constructor of a GStreamer Media
|
||||
*
|
||||
*/
|
||||
MediaPlayer(string name);
|
||||
/**
|
||||
* Destructor.
|
||||
*
|
||||
*/
|
||||
~MediaPlayer();
|
||||
/**
|
||||
* Open a media using gstreamer URI
|
||||
* */
|
||||
void Open(string uri);
|
||||
/**
|
||||
* True if a media was oppenned
|
||||
* */
|
||||
bool isOpen() const;
|
||||
/**
|
||||
* Close the Media
|
||||
* */
|
||||
void Close();
|
||||
/**
|
||||
* Update status
|
||||
* Must be called in update loop
|
||||
* */
|
||||
void Update();
|
||||
/**
|
||||
* Pause / Play
|
||||
* Can play backward if play speed is negative
|
||||
* */
|
||||
void Play(bool on);
|
||||
/**
|
||||
* Get Pause / Play
|
||||
* */
|
||||
bool isPlaying() const;
|
||||
/**
|
||||
* Speed factor for playing
|
||||
* Can be negative.
|
||||
* */
|
||||
double PlaySpeed() const;
|
||||
/**
|
||||
* Set the speed factor for playing
|
||||
* Can be negative.
|
||||
* */
|
||||
void SetPlaySpeed(double s);
|
||||
/**
|
||||
* True if the player will loop when at begin or end
|
||||
* */
|
||||
typedef enum {
|
||||
LOOP_NONE = 0,
|
||||
LOOP_REWIND = 1,
|
||||
LOOP_BIDIRECTIONAL = 2
|
||||
} LoopMode;
|
||||
LoopMode Loop() const;
|
||||
/**
|
||||
* Set the player to loop
|
||||
* */
|
||||
void setLoop(LoopMode mode);
|
||||
/**
|
||||
* Restart from zero
|
||||
* */
|
||||
void Rewind();
|
||||
/**
|
||||
* Seek to next frame when paused
|
||||
* Can go backward if play speed is negative
|
||||
* */
|
||||
void SeekNextFrame();
|
||||
/**
|
||||
* Seek to any position in media
|
||||
* pos in nanoseconds.
|
||||
* */
|
||||
void SeekTo(GstClockTime pos);
|
||||
/**
|
||||
* Jump by 10% of the duration
|
||||
* */
|
||||
void FastForward();
|
||||
/**
|
||||
* Get position time
|
||||
* */
|
||||
GstClockTime Position();
|
||||
/**
|
||||
* Get total duration time
|
||||
* */
|
||||
GstClockTime Duration();
|
||||
/**
|
||||
* Get duration of one frame
|
||||
* */
|
||||
GstClockTime FrameDuration();
|
||||
/**
|
||||
* Get framerate of the media
|
||||
* */
|
||||
double FrameRate() const;
|
||||
/**
|
||||
* Get rendering update framerate
|
||||
* measured during play
|
||||
* */
|
||||
double UpdateFrameRate() const;
|
||||
/**
|
||||
* Bind / Get the OpenGL texture
|
||||
* Must be called in OpenGL context
|
||||
* */
|
||||
void Bind();
|
||||
guint Texture() const;
|
||||
/**
|
||||
* Get Image properties
|
||||
* */
|
||||
guint Width() const;
|
||||
guint Height() const;
|
||||
float AspectRatio() const;
|
||||
|
||||
private:
|
||||
|
||||
string id;
|
||||
string uri;
|
||||
guint textureindex;
|
||||
guint width;
|
||||
guint height;
|
||||
GstClockTime position;
|
||||
GstClockTime start_position;
|
||||
GstClockTime duration;
|
||||
GstClockTime frame_duration;
|
||||
gdouble rate;
|
||||
LoopMode loop;
|
||||
TimeCounter timecount;
|
||||
gdouble framerate;
|
||||
GstState desired_state;
|
||||
GstElement *pipeline;
|
||||
GstDiscoverer *discoverer;
|
||||
std::stringstream discoverer_message;
|
||||
GstVideoFrame v_frame;
|
||||
GstVideoInfo v_frame_video_info;
|
||||
std::atomic<bool> v_frame_is_full;
|
||||
std::atomic<bool> need_loop;
|
||||
|
||||
bool ready;
|
||||
bool seekable;
|
||||
bool isimage;
|
||||
|
||||
void execute_open();
|
||||
void execute_loop_command();
|
||||
void execute_seek_command(GstClockTime target = GST_CLOCK_TIME_NONE);
|
||||
bool fill_v_frame(GstBuffer *buf);
|
||||
|
||||
static GstFlowReturn callback_pull_sample_video (GstElement *bin, MediaPlayer *m);
|
||||
static GstFlowReturn callback_get_last_sample_video (GstElement *bin, MediaPlayer *m);
|
||||
static void callback_end_of_video (GstElement *bin, MediaPlayer *m);
|
||||
static void callback_discoverer_process (GstDiscoverer *discoverer, GstDiscovererInfo *info, GError *err, MediaPlayer *m);
|
||||
static void callback_discoverer_finished(GstDiscoverer *discoverer, MediaPlayer *m);
|
||||
|
||||
};
|
||||
|
||||
|
||||
|
||||
#endif // __GST_MEDIA_PLAYER_H_
|
||||
504
RenderingManager.cpp
Normal file
504
RenderingManager.cpp
Normal file
@@ -0,0 +1,504 @@
|
||||
#include <cstring>
|
||||
#include <thread>
|
||||
#include <mutex>
|
||||
#include <chrono>
|
||||
#include <assert.h>
|
||||
|
||||
// Desktop OpenGL function loader
|
||||
#include <glad/glad.h> // Initialized with gladLoadGLLoader()
|
||||
|
||||
// Include glfw3.h after our OpenGL definitions
|
||||
#define GLFW_INCLUDE_GLEXT
|
||||
#include <GLFW/glfw3.h>
|
||||
|
||||
#ifdef APPLE
|
||||
#include "osx/CocoaToolkit.h"
|
||||
#define GLFW_EXPOSE_NATIVE_COCOA
|
||||
#define GLFW_EXPOSE_NATIVE_NSGL
|
||||
#else
|
||||
#define GLFW_EXPOSE_NATIVE_X11
|
||||
#define GLFW_EXPOSE_NATIVE_GLX
|
||||
#endif
|
||||
#include <GLFW/glfw3native.h>
|
||||
|
||||
#include <glm/ext/matrix_transform.hpp> // glm::translate, glm::rotate, glm::scale
|
||||
#include <glm/ext/matrix_clip_space.hpp> // glm::perspective
|
||||
|
||||
// Include GStreamer
|
||||
#include <gst/gl/gl.h>
|
||||
#include <gst/gl/gstglcontext.h>
|
||||
|
||||
#ifdef GLFW_EXPOSE_NATIVE_COCOA
|
||||
#include <gst/gl/cocoa/gstgldisplay_cocoa.h>
|
||||
#endif
|
||||
#ifdef GLFW_EXPOSE_NATIVE_GLX
|
||||
#include <gst/gl/x11/gstgldisplay_x11.h>
|
||||
#endif
|
||||
|
||||
// standalone image loader
|
||||
#include <stb_image.h>
|
||||
#include <stb_image_write.h>
|
||||
|
||||
// vmix
|
||||
#include "defines.h"
|
||||
#include "Log.h"
|
||||
#include "ResourceManager.h"
|
||||
#include "SettingsManager.h"
|
||||
#include "UserInterfaceManager.h"
|
||||
#include "RenderingManager.h"
|
||||
|
||||
// Class statics
|
||||
GLFWwindow* Rendering::window = nullptr;
|
||||
std::string Rendering::glsl_version;
|
||||
int Rendering::render_width = 0;
|
||||
int Rendering::render_height = 0;
|
||||
std::list<Rendering::RenderingCallback> Rendering::drawCallbacks;
|
||||
Screenshot Rendering::window_screenshot;
|
||||
bool Rendering::request_screenshot = false;
|
||||
|
||||
// local statics
|
||||
static GstGLContext *global_gl_context = NULL;
|
||||
static GstGLDisplay *global_display = NULL;
|
||||
static guintptr global_window_handle = 0;
|
||||
|
||||
|
||||
static void glfw_error_callback(int error, const char* description)
|
||||
{
|
||||
std::string msg = "Glfw Error: " + std::string(description);
|
||||
UserInterface::Error(msg, APP_TITLE);
|
||||
}
|
||||
|
||||
static void WindowRefreshCallback( GLFWwindow* window )
|
||||
{
|
||||
Rendering::Draw();
|
||||
}
|
||||
|
||||
glm::mat4 Rendering::Projection() {
|
||||
glm::mat4 projection = glm::ortho(-5.0, 5.0, -5.0, 5.0);
|
||||
glm::mat4 scale = glm::scale(glm::mat4(1.0f), glm::vec3(1.f, AspectRatio(), 1.f));
|
||||
|
||||
return projection * scale;
|
||||
}
|
||||
|
||||
|
||||
bool Rendering::Init()
|
||||
{
|
||||
// Setup window
|
||||
glfwSetErrorCallback(glfw_error_callback);
|
||||
if (!glfwInit()){
|
||||
UserInterface::Error("Failed to Initialize GLFW.");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Decide GL+GLSL versions GL 3.2 + GLSL 150
|
||||
glsl_version = "#version 150";
|
||||
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
|
||||
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 2);
|
||||
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); // 3.2+ only
|
||||
#if __APPLE__
|
||||
glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE); // Required on Mac
|
||||
#endif
|
||||
|
||||
WindowSettings winset = Settings::application.windows.front();
|
||||
|
||||
// Create window with graphics context
|
||||
glfwWindowHint(GLFW_VISIBLE, GLFW_FALSE);
|
||||
window = glfwCreateWindow(winset.w, winset.h, winset.name.c_str(), NULL, NULL);
|
||||
if (window == NULL){
|
||||
UserInterface::Error("Failed to Create GLFW Window.");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Add application icon
|
||||
size_t fpsize = 0;
|
||||
const char *fp = Resource::getData("images/glmixer_256x256.png", &fpsize);
|
||||
if (fp != nullptr) {
|
||||
GLFWimage icon;
|
||||
icon.pixels = stbi_load_from_memory( (const stbi_uc*)fp, fpsize, &icon.width, &icon.height, nullptr, 4 );
|
||||
glfwSetWindowIcon( window, 1, &icon );
|
||||
free( icon.pixels );
|
||||
}
|
||||
|
||||
glfwSetWindowPos(window, winset.x, winset.y);
|
||||
glfwMakeContextCurrent(window);
|
||||
glfwSwapInterval(1); // Enable vsync3
|
||||
glfwSetWindowRefreshCallback( window, WindowRefreshCallback );
|
||||
|
||||
// Initialize OpenGL loader
|
||||
bool err = gladLoadGLLoader((GLADloadproc) glfwGetProcAddress) == 0;
|
||||
if (err) {
|
||||
UserInterface::Error("Failed to initialize OpenGL loader.");
|
||||
return false;
|
||||
}
|
||||
|
||||
// show window
|
||||
glfwShowWindow(window);
|
||||
// restore fullscreen
|
||||
if (winset.fullscreen)
|
||||
ToggleFullscreen();
|
||||
|
||||
// Rendering area (not necessarily same as window)
|
||||
glfwGetFramebufferSize(window, &render_width, &render_height);
|
||||
glViewport(0, 0, render_width, render_height);
|
||||
|
||||
// Gstreamer link to context
|
||||
g_setenv ("GST_GL_API", "opengl3", FALSE);
|
||||
gst_init (NULL, NULL);
|
||||
|
||||
#if GST_GL_HAVE_PLATFORM_WGL
|
||||
context =
|
||||
gst_gl_context_new_wrapped (display, (guintptr) wglGetCurrentContext (),
|
||||
GST_GL_PLATFORM_WGL, GST_GL_API_OPENGL);
|
||||
#elif GST_GL_HAVE_PLATFORM_CGL
|
||||
|
||||
// GstGLDisplay* display = gst_gl_display_new();
|
||||
// guintptr context = gst_gl_context_get_current_gl_context(GST_GL_HAVE_PLATFORM_CGL);
|
||||
// GstGLContext* gstcontext = gst_gl_context_new_wrapped(display, context, GST_GL_HAVE_PLATFORM_CGL, GST_GL_API_OPENGL);
|
||||
// GstGLDisplay *gst_display;
|
||||
|
||||
|
||||
// GstGLDisplay *gst_display;
|
||||
|
||||
//global_display = GST_GL_DISPLAY ( glfwGetCocoaMonitor(window) );
|
||||
|
||||
//GstGLDisplayCocoa *tmp = gst_gl_display_cocoa_new ();
|
||||
|
||||
global_display = GST_GL_DISPLAY (gst_gl_display_cocoa_new ());
|
||||
|
||||
//glfwMakeContextCurrent(window);
|
||||
|
||||
global_gl_context = gst_gl_context_new_wrapped (global_display,
|
||||
(guintptr) 0,
|
||||
GST_GL_PLATFORM_CGL, GST_GL_API_OPENGL);
|
||||
|
||||
// id toto = glfwGetNSGLContext(window);
|
||||
|
||||
// g_warning("OpenGL Contexts %ld %ld", (long int) glfwGetNSGLContext(window), (long int) CocoaToolkit::get_current_nsopengl_context() );
|
||||
|
||||
// global_gl_context = gst_gl_context_new_wrapped (global_display,
|
||||
// (guintptr) glfwGetNSGLContext(window),
|
||||
// GST_GL_PLATFORM_CGL, GST_GL_API_OPENGL);
|
||||
|
||||
// guintptr context;
|
||||
// context = gst_gl_context_get_current_gl_context (GST_GL_PLATFORM_CGL);
|
||||
|
||||
// gst_display = gst_gl_display_new ();
|
||||
|
||||
// global_gl_context = gst_gl_context_new_wrapped (GST_GL_DISPLAY (gst_display),
|
||||
// context, GST_GL_PLATFORM_CGL, gst_gl_context_get_current_gl_api (GST_GL_PLATFORM_CGL, NULL, NULL));
|
||||
|
||||
// global_display = GST_GL_DISPLAY ( gst_gl_context_get_display(global_gl_context) );
|
||||
|
||||
// global_window_handle = (guintptr) glfwGetCocoaWindow(window);
|
||||
|
||||
#elif GST_GL_HAVE_PLATFORM_GLX
|
||||
|
||||
//
|
||||
global_display = (GstGLDisplay*) gst_gl_display_x11_new_with_display( glfwGetX11Display() );
|
||||
|
||||
global_gl_context = gst_gl_context_new_wrapped (global_display,
|
||||
(guintptr) glfwGetGLXContext(window),
|
||||
GST_GL_PLATFORM_GLX, GST_GL_API_OPENGL);
|
||||
|
||||
global_window_handle = (guintptr) glfwGetX11Window(window);
|
||||
|
||||
#endif
|
||||
|
||||
// TODO : force GPU decoding
|
||||
|
||||
// GstElementFactory *vdpauh264dec = gst_element_factory_find("vdpauh264dec");
|
||||
// if (vdpauh264dec)
|
||||
// gst_plugin_feature_set_rank (GST_PLUGIN_FEATURE(vdpauh264dec), GST_RANK_PRIMARY); // or GST_RANK_MARGINAL
|
||||
// GstElementFactory *vdpaumpeg4dec = gst_element_factory_find("vdpaumpeg4dec");
|
||||
// if (vdpaumpeg4dec)
|
||||
// gst_plugin_feature_set_rank (GST_PLUGIN_FEATURE(vdpaumpeg4dec), GST_RANK_PRIMARY);
|
||||
// GstElementFactory *vdpaumpegdec = gst_element_factory_find("vdpaumpegdec");
|
||||
// if (vdpaumpegdec)
|
||||
// gst_plugin_feature_set_rank (GST_PLUGIN_FEATURE(vdpaumpegdec), GST_RANK_PRIMARY);
|
||||
|
||||
// file drop callback
|
||||
glfwSetDropCallback(window, Rendering::FileDropped);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Rendering::isActive()
|
||||
{
|
||||
return !glfwWindowShouldClose(window);
|
||||
}
|
||||
|
||||
|
||||
void Rendering::AddDrawCallback(RenderingCallback function)
|
||||
{
|
||||
|
||||
drawCallbacks.push_back(function);
|
||||
|
||||
}
|
||||
|
||||
void Rendering::Draw()
|
||||
{
|
||||
if (Rendering::Begin() )
|
||||
{
|
||||
UserInterface::NewFrame();
|
||||
|
||||
std::list<Rendering::RenderingCallback>::iterator iter;
|
||||
for (iter=drawCallbacks.begin(); iter != drawCallbacks.end(); iter++)
|
||||
{
|
||||
(*iter)();
|
||||
}
|
||||
|
||||
UserInterface::Render();
|
||||
Rendering::End();
|
||||
}
|
||||
|
||||
// no g_main_loop_run(loop) : update global GMainContext
|
||||
g_main_context_iteration(NULL, FALSE);
|
||||
|
||||
}
|
||||
|
||||
bool Rendering::Begin()
|
||||
{
|
||||
// Poll and handle events (inputs, window resize, etc.)
|
||||
// You can read the io.WantCaptureMouse, io.WantCaptureKeyboard flags to tell if dear imgui wants to use your inputs.
|
||||
// - When io.WantCaptureMouse is true, do not dispatch mouse input data to your main application.
|
||||
// - When io.WantCaptureKeyboard is true, do not dispatch keyboard input data to your main application.
|
||||
// Generally you may always pass all inputs to dear imgui, and hide them from your application based on those two flags.
|
||||
glfwPollEvents();
|
||||
|
||||
glfwMakeContextCurrent(window);
|
||||
if( glfwGetWindowAttrib( window, GLFW_ICONIFIED ) )
|
||||
{
|
||||
std::this_thread::sleep_for( std::chrono::milliseconds( 50 ) );
|
||||
return false;
|
||||
}
|
||||
|
||||
// handle window resize
|
||||
glfwGetFramebufferSize(window, &render_width, &render_height);
|
||||
|
||||
// handle window resize
|
||||
glViewport(0, 0, render_width, render_height);
|
||||
|
||||
// GL Colors
|
||||
glClearColor(0.2f, 0.2f, 0.2f, 1.f);
|
||||
glClear(GL_COLOR_BUFFER_BIT);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void Rendering::End()
|
||||
{
|
||||
glfwMakeContextCurrent(window);
|
||||
|
||||
// perform screenshot if requested
|
||||
if (request_screenshot) {
|
||||
window_screenshot.CreateFromCaptureGL(0, 0, render_width, render_height);
|
||||
request_screenshot = false;
|
||||
}
|
||||
|
||||
// swap GL buffers
|
||||
glfwSwapBuffers(window);
|
||||
}
|
||||
|
||||
|
||||
void Rendering::Terminate()
|
||||
{
|
||||
// settings
|
||||
if ( !Settings::application.windows.front().fullscreen) {
|
||||
int x, y;
|
||||
glfwGetWindowPos(window, &x, &y);
|
||||
Settings::application.windows.front().x = x;
|
||||
Settings::application.windows.front().y = y;
|
||||
glfwGetWindowSize(window,&x, &y);
|
||||
Settings::application.windows.front().w = x;
|
||||
Settings::application.windows.front().h = y;
|
||||
}
|
||||
|
||||
// close window
|
||||
glfwDestroyWindow(window);
|
||||
glfwTerminate();
|
||||
}
|
||||
|
||||
|
||||
void Rendering::Close()
|
||||
{
|
||||
glfwSetWindowShouldClose(window, true);
|
||||
}
|
||||
|
||||
void Rendering::ToggleFullscreen()
|
||||
{
|
||||
// if in fullscreen mode
|
||||
if (glfwGetWindowMonitor(window) != nullptr) {
|
||||
// set to window mode
|
||||
glfwSetWindowMonitor( window, nullptr, Settings::application.windows.front().x,
|
||||
Settings::application.windows.front().y,
|
||||
Settings::application.windows.front().w,
|
||||
Settings::application.windows.front().h, 0 );
|
||||
Settings::application.windows.front().fullscreen = false;
|
||||
}
|
||||
// not in fullscreen mode
|
||||
else {
|
||||
// remember window geometry
|
||||
int x, y;
|
||||
glfwGetWindowPos(window, &x, &y);
|
||||
Settings::application.windows.front().x = x;
|
||||
Settings::application.windows.front().y = y;
|
||||
glfwGetWindowSize(window,&x, &y);
|
||||
Settings::application.windows.front().w = x;
|
||||
Settings::application.windows.front().h = y;
|
||||
|
||||
// select monitor
|
||||
GLFWmonitor* monitor = glfwGetPrimaryMonitor();
|
||||
const GLFWvidmode * mode = glfwGetVideoMode(monitor);
|
||||
|
||||
// set to fullscreen mode
|
||||
glfwSetWindowMonitor( window, monitor, 0, 0, mode->width, mode->height, mode->refreshRate);
|
||||
Settings::application.windows.front().fullscreen = true;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
float Rendering::AspectRatio()
|
||||
{
|
||||
return static_cast<float>(render_width) / static_cast<float>(render_height);
|
||||
}
|
||||
|
||||
|
||||
static GstBusSyncReply
|
||||
bus_sync_handler (GstBus * bus, GstMessage * msg, gpointer user_data)
|
||||
{
|
||||
if (GST_MESSAGE_TYPE(msg) == GST_MESSAGE_NEED_CONTEXT) {
|
||||
const gchar* contextType;
|
||||
gst_message_parse_context_type(msg, &contextType);
|
||||
|
||||
if (!g_strcmp0(contextType, GST_GL_DISPLAY_CONTEXT_TYPE)) {
|
||||
GstContext *displayContext = gst_context_new(GST_GL_DISPLAY_CONTEXT_TYPE, TRUE);
|
||||
gst_context_set_gl_display(displayContext, global_display);
|
||||
gst_element_set_context(GST_ELEMENT(msg->src), displayContext);
|
||||
gst_context_unref (displayContext);
|
||||
|
||||
g_info ("Managed %s\n", contextType);
|
||||
}
|
||||
if (!g_strcmp0(contextType, "gst.gl.app_context")) {
|
||||
GstContext *appContext = gst_context_new("gst.gl.app_context", TRUE);
|
||||
GstStructure* structure = gst_context_writable_structure(appContext);
|
||||
gst_structure_set(structure, "context", GST_TYPE_GL_CONTEXT, global_gl_context, nullptr);
|
||||
gst_element_set_context(GST_ELEMENT(msg->src), appContext);
|
||||
gst_context_unref (appContext);
|
||||
|
||||
g_info ("Managed %s\n", contextType);
|
||||
}
|
||||
}
|
||||
|
||||
gst_message_unref (msg);
|
||||
|
||||
return GST_BUS_DROP;
|
||||
}
|
||||
|
||||
void Rendering::LinkPipeline( GstPipeline *pipeline )
|
||||
{
|
||||
// capture bus signals to force a unique opengl context for all GST elements
|
||||
GstBus* m_bus = gst_pipeline_get_bus (GST_PIPELINE (pipeline));
|
||||
gst_bus_set_sync_handler (m_bus, (GstBusSyncHandler) bus_sync_handler, pipeline, NULL);
|
||||
gst_object_unref (m_bus);
|
||||
|
||||
|
||||
// GstBus* m_bus = gst_pipeline_get_bus (GST_PIPELINE (pipeline));
|
||||
// gst_bus_enable_sync_message_emission (m_bus);
|
||||
// g_signal_connect (m_bus, "sync-message", G_CALLBACK (bus_sync_handler), pipeline);
|
||||
// gst_object_unref (m_bus);
|
||||
}
|
||||
|
||||
void Rendering::FileDropped(GLFWwindow* window, int path_count, const char* paths[])
|
||||
{
|
||||
for (int i = 0; i < path_count; ++i) {
|
||||
|
||||
Log::Info("Dropped file %s\n", paths[i]);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Screenshot *Rendering::CurrentScreenshot()
|
||||
{
|
||||
return &window_screenshot;
|
||||
}
|
||||
|
||||
void Rendering::RequestScreenshot()
|
||||
{
|
||||
window_screenshot.Clear();
|
||||
request_screenshot = true;
|
||||
}
|
||||
|
||||
void Screenshot::CreateEmpty(int w, int h)
|
||||
{
|
||||
Clear();
|
||||
Width = w;
|
||||
Height = h;
|
||||
Data = (unsigned int*) malloc(Width * Height * 4 * sizeof(unsigned int));
|
||||
memset(Data, 0, Width * Height * 4);
|
||||
}
|
||||
|
||||
void Screenshot::CreateFromCaptureGL(int x, int y, int w, int h)
|
||||
{
|
||||
Clear();
|
||||
Width = w;
|
||||
Height = h;
|
||||
Data = (unsigned int*) malloc(Width * Height * 4);
|
||||
|
||||
// actual capture of frame buffer
|
||||
glPixelStorei(GL_PACK_ALIGNMENT, 1);
|
||||
glReadPixels(x, y, w, h, GL_RGBA, GL_UNSIGNED_BYTE, Data);
|
||||
|
||||
// make it usable
|
||||
RemoveAlpha();
|
||||
FlipVertical();
|
||||
}
|
||||
|
||||
void Screenshot::SaveFile(const char* filename)
|
||||
{
|
||||
if (Data)
|
||||
stbi_write_png(filename, Width, Height, 4, Data, Width * 4);
|
||||
}
|
||||
|
||||
void Screenshot::RemoveAlpha()
|
||||
{
|
||||
unsigned int* p = Data;
|
||||
int n = Width * Height;
|
||||
while (n-- > 0)
|
||||
{
|
||||
*p |= 0xFF000000;
|
||||
p++;
|
||||
}
|
||||
}
|
||||
|
||||
void Screenshot::BlitTo(Screenshot* dst, int src_x, int src_y, int dst_x, int dst_y, int w, int h) const
|
||||
{
|
||||
const Screenshot* src = this;
|
||||
assert(dst != src);
|
||||
assert(dst != NULL);
|
||||
assert(src_x >= 0 && src_y >= 0);
|
||||
assert(src_x + w <= src->Width);
|
||||
assert(src_y + h <= src->Height);
|
||||
assert(dst_x >= 0 && dst_y >= 0);
|
||||
assert(dst_x + w <= dst->Width);
|
||||
assert(dst_y + h <= dst->Height);
|
||||
for (int y = 0; y < h; y++)
|
||||
memcpy(dst->Data + dst_x + (dst_y + y) * dst->Width, src->Data + src_x + (src_y + y) * src->Width, w * 4);
|
||||
}
|
||||
|
||||
void Screenshot::FlipVertical()
|
||||
{
|
||||
int comp = 4;
|
||||
int stride = Width * comp;
|
||||
unsigned char* line_tmp = new unsigned char[stride];
|
||||
unsigned char* line_a = (unsigned char*)Data;
|
||||
unsigned char* line_b = (unsigned char*)Data + (stride * (Height - 1));
|
||||
while (line_a < line_b)
|
||||
{
|
||||
memcpy(line_tmp, line_a, stride);
|
||||
memcpy(line_a, line_b, stride);
|
||||
memcpy(line_b, line_tmp, stride);
|
||||
line_a += stride;
|
||||
line_b -= stride;
|
||||
}
|
||||
delete[] line_tmp;
|
||||
}
|
||||
95
RenderingManager.h
Normal file
95
RenderingManager.h
Normal file
@@ -0,0 +1,95 @@
|
||||
#ifndef __RENDERING_MANAGER_H_
|
||||
#define __RENDERING_MANAGER_H_
|
||||
|
||||
#include <string>
|
||||
#include <list>
|
||||
|
||||
#include <gst/gl/gl.h>
|
||||
#include <glm/glm.hpp>
|
||||
|
||||
class Screenshot
|
||||
{
|
||||
int Width, Height;
|
||||
unsigned int * Data;
|
||||
|
||||
public:
|
||||
Screenshot() { Width = Height = 0; Data = nullptr; }
|
||||
~Screenshot() { Clear(); }
|
||||
|
||||
bool IsFull() { return Data != nullptr; }
|
||||
void Clear() { if (IsFull()) free(Data); Data = nullptr; }
|
||||
|
||||
void CreateEmpty(int w, int h);
|
||||
void CreateFromCaptureGL(int x, int y, int w, int h);
|
||||
|
||||
void RemoveAlpha();
|
||||
void FlipVertical();
|
||||
|
||||
void SaveFile(const char* filename);
|
||||
void BlitTo(Screenshot* dst, int src_x, int src_y, int dst_x, int dst_y, int w, int h) const;
|
||||
};
|
||||
|
||||
class Rendering
|
||||
{
|
||||
friend class UserInterface;
|
||||
|
||||
// GLFW integration in OS window management
|
||||
static class GLFWwindow* window;
|
||||
static Screenshot window_screenshot;
|
||||
static std::string glsl_version;
|
||||
static int render_width, render_height;
|
||||
static bool request_screenshot;
|
||||
|
||||
public:
|
||||
|
||||
// Initialization OpenGL and GLFW window creation
|
||||
static bool Init();
|
||||
// true if active rendering window
|
||||
static bool isActive();
|
||||
// request close of the UI (Quit the program)
|
||||
static void Close();
|
||||
// Post-loop termination
|
||||
static void Terminate();
|
||||
|
||||
// draw one frame
|
||||
static void Draw();
|
||||
// add function to call during Draw
|
||||
typedef void (* RenderingCallback)(void);
|
||||
static void AddDrawCallback(RenderingCallback function);
|
||||
// request screenshot
|
||||
static void RequestScreenshot();
|
||||
// get Screenshot
|
||||
static Screenshot *CurrentScreenshot();
|
||||
|
||||
// request fullscreen
|
||||
static void ToggleFullscreen();
|
||||
// get width of rendering area
|
||||
static float Width() { return render_width; }
|
||||
// get height of rendering area
|
||||
static float Height() { return render_height; }
|
||||
// get aspect ratio of rendering area
|
||||
static float AspectRatio();
|
||||
// get projection matrix (for sharers)
|
||||
static glm::mat4 Projection();
|
||||
|
||||
// for opengl pipile in gstreamer
|
||||
static void LinkPipeline( GstPipeline *pipeline );
|
||||
|
||||
|
||||
private:
|
||||
|
||||
// loop update to begin new frame
|
||||
static bool Begin();
|
||||
// loop update end frame
|
||||
static void End();
|
||||
// list of functions to call at each Draw
|
||||
static std::list<RenderingCallback> drawCallbacks;
|
||||
|
||||
// file drop callback
|
||||
static void FileDropped(GLFWwindow* window, int path_count, const char* paths[]);
|
||||
|
||||
|
||||
};
|
||||
|
||||
|
||||
#endif /* #define __RENDERING_MANAGER_H_ */
|
||||
225
ResourceManager.cpp
Normal file
225
ResourceManager.cpp
Normal file
@@ -0,0 +1,225 @@
|
||||
#include "defines.h"
|
||||
#include "ResourceManager.h"
|
||||
#include "Log.h"
|
||||
|
||||
#include <fstream>
|
||||
#include <sstream>
|
||||
#include <iostream>
|
||||
#include <cstring>
|
||||
|
||||
// Desktop OpenGL function loader
|
||||
#include <glad/glad.h>
|
||||
|
||||
// multiplatform message box
|
||||
#include <tinyfiledialogs.h>
|
||||
|
||||
// standalone image loader
|
||||
#include "stb_image.h"
|
||||
|
||||
// CMake Ressource Compiler
|
||||
#include <cmrc/cmrc.hpp>
|
||||
CMRC_DECLARE(vmix);
|
||||
|
||||
|
||||
std::map<std::string, unsigned int> Resource::textureIndex;
|
||||
|
||||
const char *Resource::getData(const std::string& path, size_t* out_file_size){
|
||||
|
||||
auto fs = cmrc::vmix::get_filesystem();
|
||||
cmrc::file file;
|
||||
const char* data = nullptr;
|
||||
*out_file_size = 0;
|
||||
|
||||
try {
|
||||
file = fs.open(path.c_str());
|
||||
const auto rc_size = std::distance(file.begin(), file.end());
|
||||
*out_file_size = rc_size;
|
||||
cmrc::file::iterator it = file.begin();
|
||||
data = static_cast<const char *>(it);
|
||||
}
|
||||
catch (std::system_error e) {
|
||||
std::string msg = "Could not access ressource " + std::string(path);
|
||||
tinyfd_messageBox(APP_TITLE, msg.c_str(), "ok", "error", 0);
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
std::string Resource::getText(const std::string& path){
|
||||
|
||||
auto fs = cmrc::vmix::get_filesystem();
|
||||
cmrc::file file;
|
||||
std::stringstream file_stream;
|
||||
try {
|
||||
file = fs.open(path.c_str());
|
||||
file_stream << std::string(file.begin(), file.end()) << std::endl;
|
||||
}
|
||||
catch (std::system_error e) {
|
||||
std::string msg = "Could not access ressource " + std::string(path);
|
||||
tinyfd_messageBox(APP_TITLE, msg.c_str(), "ok", "error", 0);
|
||||
}
|
||||
|
||||
return file_stream.str();
|
||||
}
|
||||
|
||||
|
||||
#define FOURCC_DXT1 0x31545844 // Equivalent to "DXT1" in ASCII
|
||||
#define FOURCC_DXT3 0x33545844 // Equivalent to "DXT3" in ASCII
|
||||
#define FOURCC_DXT5 0x35545844 // Equivalent to "DXT5" in ASCII
|
||||
|
||||
unsigned int Resource::getTextureDDS(const std::string& path)
|
||||
{
|
||||
GLuint textureID = 0;
|
||||
|
||||
if (textureIndex.count(path) > 0)
|
||||
return textureIndex[path];
|
||||
|
||||
// Get the pointer
|
||||
size_t size = 0;
|
||||
const char *fp = getData(path, &size);
|
||||
if ( size==0 ){
|
||||
std::string msg = "Empty ressource " + std::string(path);
|
||||
tinyfd_messageBox(APP_TITLE, msg.c_str(), "ok", "error", 0);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// verify the type of file = bytes [0 - 3]
|
||||
const char *filecode = fp;
|
||||
if (strncmp(filecode, "DDS ", 4) != 0){
|
||||
std::string msg = "Not a DDS image " + std::string(path);
|
||||
tinyfd_messageBox(APP_TITLE, msg.c_str(), "ok", "error", 0);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// get the surface desc = bytes [4 - 127]
|
||||
const char *header = fp + 4;
|
||||
unsigned int height = *(unsigned int*)&(header[8 ]);
|
||||
unsigned int width = *(unsigned int*)&(header[12]);
|
||||
unsigned int linearSize = *(unsigned int*)&(header[16]);
|
||||
unsigned int mipMapCount = *(unsigned int*)&(header[24]);
|
||||
unsigned int fourCC = *(unsigned int*)&(header[80]);
|
||||
|
||||
// how big is it going to be including all mipmaps?
|
||||
unsigned int bufsize;
|
||||
bufsize = mipMapCount > 1 ? linearSize * 2 : linearSize;
|
||||
|
||||
// get the buffer = bytes [128 - ]
|
||||
const char *buffer = fp + 128;
|
||||
|
||||
unsigned int components = (fourCC == FOURCC_DXT1) ? 3 : 4;
|
||||
unsigned int format;
|
||||
switch(fourCC)
|
||||
{
|
||||
case FOURCC_DXT1:
|
||||
format = GL_COMPRESSED_RGBA_S3TC_DXT1_EXT;
|
||||
break;
|
||||
case FOURCC_DXT3:
|
||||
format = GL_COMPRESSED_RGBA_S3TC_DXT3_EXT;
|
||||
break;
|
||||
case FOURCC_DXT5:
|
||||
format = GL_COMPRESSED_RGBA_S3TC_DXT5_EXT;
|
||||
break;
|
||||
default:
|
||||
{
|
||||
std::string msg = "Not a DXT1, DXT3 or DXT5 texture " + std::string(path);
|
||||
tinyfd_messageBox(APP_TITLE, msg.c_str(), "ok", "error", 0);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
// Create one OpenGL texture
|
||||
glGenTextures(1, &textureID);
|
||||
|
||||
// Bind the newly created texture
|
||||
glBindTexture(GL_TEXTURE_2D, textureID);
|
||||
glPixelStorei(GL_UNPACK_ALIGNMENT,1);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
|
||||
|
||||
unsigned int blockSize = (format == GL_COMPRESSED_RGBA_S3TC_DXT1_EXT) ? 8 : 16;
|
||||
unsigned int offset = 0;
|
||||
|
||||
// load the mipmaps
|
||||
for (unsigned int level = 0; level < mipMapCount && (width || height); ++level)
|
||||
{
|
||||
unsigned int size = ((width+3)/4)*((height+3)/4)*blockSize;
|
||||
glCompressedTexImage2D(GL_TEXTURE_2D, level, format, width, height,
|
||||
0, size, buffer + offset);
|
||||
glGenerateMipmap(GL_TEXTURE_2D);
|
||||
|
||||
offset += size;
|
||||
width /= 2;
|
||||
height /= 2;
|
||||
|
||||
// Deal with Non-Power-Of-Two textures.
|
||||
if(width < 1) width = 1;
|
||||
if(height < 1) height = 1;
|
||||
|
||||
}
|
||||
|
||||
textureIndex[path] = textureID;
|
||||
return textureID;
|
||||
}
|
||||
|
||||
|
||||
unsigned int Resource::getTextureImage(const std::string& path)
|
||||
{
|
||||
GLuint textureID = 0;
|
||||
|
||||
if (textureIndex.count(path) > 0)
|
||||
return textureIndex[path];
|
||||
|
||||
int w, h, n;
|
||||
unsigned char* img;
|
||||
|
||||
// Get the pointer
|
||||
size_t size = 0;
|
||||
const char *fp = getData(path, &size);
|
||||
if ( size==0 ){
|
||||
std::string msg = "Empty ressource " + std::string(path);
|
||||
tinyfd_messageBox(APP_TITLE, msg.c_str(), "ok", "error", 0);
|
||||
return 0;
|
||||
}
|
||||
|
||||
img = stbi_load_from_memory(reinterpret_cast<const unsigned char *>(fp), size, &w, &h, &n, 4);
|
||||
if (img == NULL) {
|
||||
std::string msg = "Failed to load " + std::string(path) + " : " + std::string(stbi_failure_reason());
|
||||
tinyfd_messageBox(APP_TITLE, msg.c_str(), "ok", "error", 0);
|
||||
return 0;
|
||||
}
|
||||
|
||||
glGenTextures(1, &textureID);
|
||||
glBindTexture( GL_TEXTURE_2D, textureID);
|
||||
|
||||
glPixelStorei(GL_UNPACK_ALIGNMENT, 4);
|
||||
//glPixelStorei(GL_UNPACK_ROW_LENGTH, 0);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
|
||||
|
||||
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, w, h, 0, GL_RGBA, GL_UNSIGNED_BYTE, img);
|
||||
|
||||
stbi_image_free(img);
|
||||
|
||||
textureIndex[path] = textureID;
|
||||
return textureID;
|
||||
}
|
||||
|
||||
void Resource::listFiles()
|
||||
{
|
||||
// enter icons directory
|
||||
auto fs = cmrc::vmix::get_filesystem();
|
||||
cmrc::file file;
|
||||
cmrc::directory_iterator it = fs.iterate_directory("");
|
||||
cmrc::directory_iterator itend = it.end();
|
||||
|
||||
// loop over all icons
|
||||
while(it != itend){
|
||||
|
||||
cmrc::directory_entry file = *it;
|
||||
|
||||
if (file.is_file()) {
|
||||
std::cerr << "Found file " << file.filename() << std::endl;
|
||||
|
||||
}
|
||||
|
||||
it++;
|
||||
}
|
||||
}
|
||||
33
ResourceManager.h
Normal file
33
ResourceManager.h
Normal file
@@ -0,0 +1,33 @@
|
||||
#ifndef vlmixer_rsc_manager
|
||||
#define vlmixer_rsc_manager
|
||||
|
||||
#include <string>
|
||||
#include <map>
|
||||
|
||||
class Resource
|
||||
{
|
||||
|
||||
static std::map<std::string, unsigned int> textureIndex;
|
||||
|
||||
static void listFiles();
|
||||
|
||||
public:
|
||||
|
||||
// Support all text files
|
||||
// return file tyext content as one string
|
||||
static std::string getText(const std::string& path);
|
||||
|
||||
// Support DDS files, DXT1, DXT5 and DXT5
|
||||
// Returns the OpenGL generated Texture index
|
||||
static unsigned int getTextureDDS(const std::string& path);
|
||||
|
||||
// Support PNG, JPEG, TGA, BMP, PSD, GIF, HDR, PIC, PNM
|
||||
// Returns the OpenGL generated Texture index
|
||||
static unsigned int getTextureImage(const std::string& path);
|
||||
|
||||
// Generic access to pointer to data
|
||||
static const char *getData(const std::string& path, size_t* out_file_size);
|
||||
|
||||
};
|
||||
|
||||
#endif /* vlmixer_rsc_manager */
|
||||
130
SettingsManager.cpp
Normal file
130
SettingsManager.cpp
Normal file
@@ -0,0 +1,130 @@
|
||||
#include "SettingsManager.h"
|
||||
#include "defines.h"
|
||||
|
||||
#include <iostream>
|
||||
|
||||
#include <tinyxml2.h>
|
||||
using namespace tinyxml2;
|
||||
|
||||
#ifndef XMLCheckResult
|
||||
#define XMLCheckResult(a_eResult) if (a_eResult != XML_SUCCESS) { printf("Settings warning: %i\n", a_eResult); return; }
|
||||
#endif
|
||||
|
||||
AppSettings Settings::application(APP_NAME);
|
||||
|
||||
AppSettings::AppSettings(const string& name) {
|
||||
|
||||
this->name = name;
|
||||
this->filename = "./" + name + ".xml";
|
||||
this->windows.clear();
|
||||
this->windows.push_back(WindowSettings(15,15,1280,720, APP_TITLE));
|
||||
this->scale = 1.0;
|
||||
this->color = 0;
|
||||
}
|
||||
|
||||
void Settings::Save()
|
||||
{
|
||||
XMLDocument xmlDoc;
|
||||
string s;
|
||||
|
||||
XMLDeclaration *pDec = xmlDoc.NewDeclaration();
|
||||
xmlDoc.InsertFirstChild(pDec);
|
||||
|
||||
XMLElement *pRoot = xmlDoc.NewElement(application.name.c_str());
|
||||
xmlDoc.InsertEndChild(pRoot);
|
||||
|
||||
s="Settings for "+application.name;
|
||||
XMLComment *pComment = xmlDoc.NewComment(s.c_str());
|
||||
pRoot->InsertEndChild(pComment);
|
||||
|
||||
// block: windows
|
||||
{
|
||||
XMLElement *windowsNode = xmlDoc.NewElement( "Windows" );
|
||||
|
||||
list<WindowSettings>::iterator iter;
|
||||
for (iter=application.windows.begin(); iter != application.windows.end(); iter++)
|
||||
{
|
||||
const WindowSettings& w=*iter;
|
||||
|
||||
XMLElement *window = xmlDoc.NewElement( "Window" );
|
||||
window->SetAttribute("name", w.name.c_str());
|
||||
window->SetAttribute("x", w.x);
|
||||
window->SetAttribute("y", w.y);
|
||||
window->SetAttribute("w", w.w);
|
||||
window->SetAttribute("h", w.h);
|
||||
window->SetAttribute("f", w.fullscreen);
|
||||
windowsNode->InsertEndChild(window);
|
||||
}
|
||||
|
||||
pRoot->InsertEndChild(windowsNode);
|
||||
}
|
||||
|
||||
XMLElement *applicationNode = xmlDoc.NewElement( "Application" );
|
||||
applicationNode->SetAttribute("scale", application.scale);
|
||||
applicationNode->SetAttribute("color", application.color);
|
||||
pRoot->InsertEndChild(applicationNode);
|
||||
|
||||
XMLError eResult = xmlDoc.SaveFile(application.filename.c_str());
|
||||
XMLCheckResult(eResult);
|
||||
}
|
||||
|
||||
void Settings::Load()
|
||||
{
|
||||
XMLDocument xmlDoc;
|
||||
XMLError eResult = xmlDoc.LoadFile(application.filename.c_str());
|
||||
|
||||
// do not warn if non existing file
|
||||
if (eResult == XML_ERROR_FILE_NOT_FOUND)
|
||||
return;
|
||||
// warn on other errors
|
||||
XMLCheckResult(eResult);
|
||||
|
||||
XMLElement *pRoot = xmlDoc.FirstChildElement(application.name.c_str());
|
||||
if (pRoot == nullptr) return;
|
||||
|
||||
if (application.name.compare( string( pRoot->Value() ) ) != 0 )
|
||||
// different root name
|
||||
return;
|
||||
|
||||
// block: windows
|
||||
{
|
||||
application.windows.clear(); // trash existing list
|
||||
|
||||
XMLElement * pElement = pRoot->FirstChildElement("Windows");
|
||||
if (pElement == nullptr) return;
|
||||
|
||||
XMLElement* pWindowNode = pElement->FirstChildElement("Window");
|
||||
for( ; pWindowNode ; pWindowNode=pWindowNode->NextSiblingElement())
|
||||
{
|
||||
WindowSettings w;
|
||||
const char *pName=pWindowNode->Attribute("name");
|
||||
if (pName) w.name=pName;
|
||||
|
||||
pWindowNode->QueryIntAttribute("x", &w.x); // If this fails, original value is left as-is
|
||||
pWindowNode->QueryIntAttribute("y", &w.y);
|
||||
pWindowNode->QueryIntAttribute("w", &w.w);
|
||||
pWindowNode->QueryIntAttribute("h", &w.h);
|
||||
pWindowNode->QueryBoolAttribute("f", &w.fullscreen);
|
||||
|
||||
application.windows.push_back(w);
|
||||
}
|
||||
}
|
||||
|
||||
XMLElement * pElement = pRoot->FirstChildElement("Application");
|
||||
if (pElement == nullptr) return;
|
||||
pElement->QueryFloatAttribute("scale", &application.scale);
|
||||
pElement->QueryIntAttribute("color", &application.color);
|
||||
|
||||
}
|
||||
|
||||
|
||||
void Settings::Check()
|
||||
{
|
||||
Settings::Save();
|
||||
|
||||
XMLDocument xmlDoc;
|
||||
XMLError eResult = xmlDoc.LoadFile(application.filename.c_str());
|
||||
XMLCheckResult(eResult);
|
||||
|
||||
xmlDoc.Print();
|
||||
}
|
||||
722
UserInterfaceManager.cpp
Normal file
722
UserInterfaceManager.cpp
Normal file
@@ -0,0 +1,722 @@
|
||||
#include <iostream>
|
||||
#include <cstring>
|
||||
#include <fstream>
|
||||
#include <sstream>
|
||||
|
||||
// ImGui
|
||||
#include "imgui.h"
|
||||
#define IMGUI_DEFINE_MATH_OPERATORS
|
||||
#include "imgui_internal.h"
|
||||
#include "imgui_impl_glfw.h"
|
||||
#include "imgui_impl_opengl3.h"
|
||||
|
||||
// Desktop OpenGL function loader
|
||||
#include <glad/glad.h>
|
||||
|
||||
// Include glfw3.h after our OpenGL definitions
|
||||
#include <GLFW/glfw3.h>
|
||||
|
||||
// multiplatform
|
||||
#include <tinyfiledialogs.h>
|
||||
|
||||
// generic image loader
|
||||
#define STB_IMAGE_IMPLEMENTATION
|
||||
#include <stb_image.h>
|
||||
#define STB_IMAGE_WRITE_IMPLEMENTATION
|
||||
#include <stb_image_write.h>
|
||||
|
||||
#ifdef _WIN32
|
||||
# include <windows.h>
|
||||
# include <shellapi.h>
|
||||
#endif
|
||||
|
||||
#include "defines.h"
|
||||
#include "Log.h"
|
||||
#include "TextEditor.h"
|
||||
#include "UserInterfaceManager.h"
|
||||
#include "RenderingManager.h"
|
||||
#include "ResourceManager.h"
|
||||
#include "SettingsManager.h"
|
||||
#include "FileDialog.h"
|
||||
#include "ImGuiToolkit.h"
|
||||
|
||||
MessageBox UserInterface::warnings;
|
||||
MainWindow UserInterface::mainwindow;
|
||||
std::string UserInterface::currentFileDialog = "";
|
||||
std::string UserInterface::currentTextEdit = "";
|
||||
|
||||
static std::thread loadThread;
|
||||
static bool loadThreadDone = false;
|
||||
static TextEditor editor;
|
||||
|
||||
|
||||
static void NativeOpenFile(std::string ext)
|
||||
{
|
||||
|
||||
char const * lTheOpenFileName;
|
||||
char const * lFilterPatterns[2] = { "*.txt", "*.text" };
|
||||
|
||||
lTheOpenFileName = tinyfd_openFileDialog( "Open a text file", "", 2, lFilterPatterns, nullptr, 0);
|
||||
|
||||
if (!lTheOpenFileName)
|
||||
{
|
||||
tinyfd_messageBox(APP_TITLE, "Open file name is NULL. ", "ok", "warning", 1);
|
||||
}
|
||||
else
|
||||
{
|
||||
char buf[1024];
|
||||
sprintf( buf, "he selected file is :\n %s", lTheOpenFileName );
|
||||
tinyfd_messageBox(APP_TITLE, buf, "ok", "info", 1);
|
||||
}
|
||||
|
||||
loadThreadDone = true;
|
||||
}
|
||||
|
||||
|
||||
static void OpenWebpage( const char* url )
|
||||
{
|
||||
#ifdef _WIN32
|
||||
ShellExecuteA( nullptr, nullptr, url, nullptr, nullptr, 0 );
|
||||
#elif defined __APPLE__
|
||||
char buf[1024];
|
||||
sprintf( buf, "open %s", url );
|
||||
system( buf );
|
||||
#else
|
||||
char buf[1024];
|
||||
sprintf( buf, "xdg-open %s", url );
|
||||
system( buf );
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
bool UserInterface::Init()
|
||||
{
|
||||
if (Rendering::window == nullptr)
|
||||
return false;
|
||||
|
||||
// Setup Dear ImGui context
|
||||
IMGUI_CHECKVERSION();
|
||||
ImGui::CreateContext();
|
||||
ImGuiIO& io = ImGui::GetIO(); (void)io;
|
||||
io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard; // Enable Keyboard Controls
|
||||
io.MouseDrawCursor = true;
|
||||
|
||||
// Setup Platform/Renderer bindings
|
||||
ImGui_ImplGlfw_InitForOpenGL(Rendering::window, true);
|
||||
ImGui_ImplOpenGL3_Init(Rendering::glsl_version.c_str());
|
||||
|
||||
// Setup Dear ImGui style
|
||||
ImGuiToolkit::SetAccentColor(static_cast<ImGuiToolkit::accent_color>(Settings::application.color));
|
||||
|
||||
// Load Fonts (using resource manager, a temporary copy of the raw data is necessary)
|
||||
ImGuiToolkit::SetFont(ImGuiToolkit::FONT_DEFAULT, "Roboto-Regular", 22);
|
||||
ImGuiToolkit::SetFont(ImGuiToolkit::FONT_BOLD, "Roboto-Bold", 22);
|
||||
ImGuiToolkit::SetFont(ImGuiToolkit::FONT_ITALIC, "Roboto-Italic", 22);
|
||||
ImGuiToolkit::SetFont(ImGuiToolkit::FONT_MONO, "Hack-Regular", 20);
|
||||
io.FontGlobalScale = Settings::application.scale;
|
||||
|
||||
// Style
|
||||
ImGuiStyle& style = ImGui::GetStyle();
|
||||
style.WindowPadding.x = 12.f;
|
||||
style.WindowPadding.y = 6.f;
|
||||
style.FramePadding.x = 10.f;
|
||||
style.FramePadding.y = 5.f;
|
||||
style.IndentSpacing = 22.f;
|
||||
style.ItemSpacing.x = 12.f;
|
||||
style.ItemSpacing.y = 4.f;
|
||||
style.ItemInnerSpacing.x = 8.f;
|
||||
style.ItemInnerSpacing.y = 3.f;
|
||||
style.WindowRounding = 8.f;
|
||||
style.ChildRounding = 4.f;
|
||||
style.FrameRounding = 4.f;
|
||||
style.GrabRounding = 2.f;
|
||||
style.GrabMinSize = 14.f;
|
||||
style.Alpha = 0.92f;
|
||||
|
||||
// prevent bug with imgui clipboard (null at start)
|
||||
ImGui::SetClipboardText("");
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void UserInterface::handleKeyboard()
|
||||
{
|
||||
ImGuiIO& io = ImGui::GetIO();
|
||||
|
||||
// Application "CTRL +"" Shortcuts
|
||||
if ( io.KeyCtrl ) {
|
||||
|
||||
if (ImGui::IsKeyPressed( GLFW_KEY_Q ))
|
||||
Rendering::Close();
|
||||
else if (ImGui::IsKeyPressed( GLFW_KEY_O ))
|
||||
UserInterface::OpenFileMedia();
|
||||
else if (ImGui::IsKeyPressed( GLFW_KEY_S ))
|
||||
std::cerr <<" Save File " << std::endl;
|
||||
else if (ImGui::IsKeyPressed( GLFW_KEY_W ))
|
||||
std::cerr <<" Close File " << std::endl;
|
||||
else if (ImGui::IsKeyPressed( GLFW_KEY_L ))
|
||||
mainwindow.ToggleLogs();
|
||||
|
||||
}
|
||||
|
||||
// Application F-Keys
|
||||
if (ImGui::IsKeyPressed( GLFW_KEY_F12 ))
|
||||
Rendering::ToggleFullscreen();
|
||||
else if (ImGui::IsKeyPressed( GLFW_KEY_F11 ))
|
||||
mainwindow.StartScreenshot();
|
||||
|
||||
}
|
||||
|
||||
void UserInterface::handleMouse()
|
||||
{
|
||||
|
||||
ImGuiIO& io = ImGui::GetIO();
|
||||
|
||||
// if not on any window
|
||||
if ( !ImGui::IsAnyWindowHovered() )
|
||||
{
|
||||
// Mouse wheel over background
|
||||
if ( io.MouseWheel > 0) {
|
||||
|
||||
|
||||
}
|
||||
|
||||
if ( ImGui::IsMouseDown(ImGuiMouseButton_Left)) {
|
||||
|
||||
}
|
||||
|
||||
if ( ImGui::IsMouseReleased(ImGuiMouseButton_Left) )
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
void UserInterface::NewFrame()
|
||||
{
|
||||
// deal with keyboard and mouse events
|
||||
handleKeyboard();
|
||||
handleMouse();
|
||||
|
||||
// Start the Dear ImGui frame
|
||||
ImGui_ImplOpenGL3_NewFrame();
|
||||
ImGui_ImplGlfw_NewFrame();
|
||||
ImGui::NewFrame();
|
||||
|
||||
// Main window
|
||||
mainwindow.Render();
|
||||
}
|
||||
|
||||
|
||||
void UserInterface::Render()
|
||||
{
|
||||
ImVec2 geometry(static_cast<float>(Rendering::render_width), static_cast<float>(Rendering::render_height));
|
||||
|
||||
// warning modal dialog
|
||||
warnings.Render( geometry.x * 0.4f ); // 40% width
|
||||
|
||||
// file modal dialog
|
||||
geometry.x *= 0.4f;
|
||||
geometry.y *= 0.4f;
|
||||
if ( !currentFileDialog.empty() && FileDialog::Instance()->Render(currentFileDialog.c_str(), geometry)) {
|
||||
if (FileDialog::Instance()->IsOk == true) {
|
||||
std::string filePathNameText = FileDialog::Instance()->GetFilepathName();
|
||||
// done
|
||||
currentFileDialog = "";
|
||||
}
|
||||
FileDialog::Instance()->CloseDialog(currentFileDialog.c_str());
|
||||
}
|
||||
|
||||
// Rendering
|
||||
ImGui::Render();
|
||||
ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData());
|
||||
}
|
||||
|
||||
void UserInterface::Terminate()
|
||||
{
|
||||
// Cleanup
|
||||
ImGui_ImplOpenGL3_Shutdown();
|
||||
ImGui_ImplGlfw_Shutdown();
|
||||
ImGui::DestroyContext();
|
||||
}
|
||||
|
||||
void UserInterface::Error(const string& text, const string& title)
|
||||
{
|
||||
// Show NON Graphical dialog (using multiplatform tiny file dialog )
|
||||
string t = title.empty() ? APP_TITLE : title;
|
||||
tinyfd_messageBox( t.c_str(), text.c_str(), "ok", "error", 0);
|
||||
|
||||
}
|
||||
|
||||
void UserInterface::Warning(const char* fmt, ...)
|
||||
{
|
||||
va_list args;
|
||||
va_start(args, fmt);
|
||||
|
||||
ImGuiTextBuffer buf;
|
||||
buf.appendfv(fmt, args);
|
||||
warnings.messages.push_back(buf.c_str());
|
||||
Log::Info(buf.c_str());
|
||||
|
||||
va_end(args);
|
||||
}
|
||||
|
||||
MessageBox::MessageBox()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
void MessageBox::Render(float width)
|
||||
{
|
||||
if (messages.empty())
|
||||
return;
|
||||
|
||||
ImGui::OpenPopup("Warning");
|
||||
if (ImGui::BeginPopupModal("Warning", NULL, ImGuiWindowFlags_AlwaysAutoResize))
|
||||
{
|
||||
ImGuiToolkit::Icon(9, 4);
|
||||
ImGui::SameLine(0, 10);
|
||||
ImGui::TextColored(ImVec4(1.0f,0.6f,0.0f,1.0f), "%ld error(s) occured.\n\n", messages.size());
|
||||
|
||||
ImGui::PushTextWrapPos(ImGui::GetCursorPos().x + width);
|
||||
for (list<string>::iterator it=messages.begin(); it != messages.end(); ++it) {
|
||||
ImGui::Text("%s\n", (*it).c_str());
|
||||
ImGui::Separator();
|
||||
}
|
||||
ImGui::PopTextWrapPos();
|
||||
|
||||
ImGui::Dummy(ImVec2(width * 0.8f, 0)); ImGui::SameLine(); // right align
|
||||
if (ImGui::Button("Ok", ImVec2(width * 0.2f, 0))) {
|
||||
ImGui::CloseCurrentPopup();
|
||||
// messages have been seen
|
||||
messages.clear();
|
||||
}
|
||||
ImGui::SetItemDefaultFocus();
|
||||
ImGui::EndPopup();
|
||||
}
|
||||
}
|
||||
|
||||
// template info panel
|
||||
inline void InfosPane(std::string vFilter, bool *vCantContinue)
|
||||
// if vCantContinue is false, the user cant validate the dialog
|
||||
{
|
||||
ImGui::TextColored(ImVec4(0, 1, 1, 1), "Infos Pane");
|
||||
|
||||
ImGui::Text("Selected Filter : %s", vFilter.c_str());
|
||||
|
||||
// File Manager information pannel
|
||||
bool canValidateDialog = false;
|
||||
ImGui::Checkbox("if not checked you cant validate the dialog", &canValidateDialog);
|
||||
|
||||
if (vCantContinue)
|
||||
*vCantContinue = canValidateDialog;
|
||||
}
|
||||
|
||||
inline void TextInfosPane(std::string vFilter, bool *vCantContinue) // if vCantContinue is false, the user cant validate the dialog
|
||||
{
|
||||
ImGui::TextColored(ImVec4(0, 1, 1, 1), "Text");
|
||||
|
||||
static std::string filepathcurrent;
|
||||
static std::string text;
|
||||
|
||||
// if different file selected
|
||||
if ( filepathcurrent.compare(FileDialog::Instance()->GetFilepathName()) != 0)
|
||||
{
|
||||
filepathcurrent = FileDialog::Instance()->GetFilepathName();
|
||||
|
||||
// fill text with file content
|
||||
std::ifstream textfilestream(filepathcurrent, std::ios::in);
|
||||
if(textfilestream.is_open()){
|
||||
std::stringstream sstr;
|
||||
sstr << textfilestream.rdbuf();
|
||||
text = sstr.str();
|
||||
textfilestream.close();
|
||||
}
|
||||
else
|
||||
{
|
||||
// empty text
|
||||
text = "";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// show text
|
||||
ImGui::PushTextWrapPos(ImGui::GetCursorPos().x + 340);
|
||||
ImGui::Text("%s", text.c_str());
|
||||
|
||||
// release Ok button if text is not empty
|
||||
if (vCantContinue)
|
||||
*vCantContinue = text.size() > 0;
|
||||
}
|
||||
|
||||
inline void ImageInfosPane(std::string vFilter, bool *vCantContinue) // if vCantContinue is false, the user cant validate the dialog
|
||||
{
|
||||
// opengl texture
|
||||
static GLuint tex = 0;
|
||||
static std::string filepathcurrent;
|
||||
static std::string message = "Please select an image (" + vFilter + ").";
|
||||
static unsigned char* img = NULL;
|
||||
static ImVec2 image_size(330, 330);
|
||||
|
||||
// generate texture (once) & clear
|
||||
if (tex == 0) {
|
||||
glGenTextures(1, &tex);
|
||||
glBindTexture( GL_TEXTURE_2D, tex);
|
||||
unsigned char clearColor[4] = {0};
|
||||
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 1, 1, 0, GL_RGBA, GL_UNSIGNED_BYTE, clearColor);
|
||||
}
|
||||
|
||||
// if different file selected
|
||||
if ( filepathcurrent.compare(FileDialog::Instance()->GetFilepathName()) != 0)
|
||||
{
|
||||
// remember path
|
||||
filepathcurrent = FileDialog::Instance()->GetFilepathName();
|
||||
|
||||
// prepare texture
|
||||
glBindTexture( GL_TEXTURE_2D, tex);
|
||||
|
||||
// load image
|
||||
int w, h, n;
|
||||
img = stbi_load(filepathcurrent.c_str(), &w, &h, &n, 4);
|
||||
if (img != NULL) {
|
||||
|
||||
// apply img to texture
|
||||
glPixelStorei(GL_UNPACK_ALIGNMENT, 4);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
|
||||
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, w, h, 0, GL_RGBA, GL_UNSIGNED_BYTE, img);
|
||||
|
||||
// adjust image display aspect ratio
|
||||
image_size.y = image_size.x * static_cast<float>(h) / static_cast<float>(w);
|
||||
|
||||
// free loaded image
|
||||
stbi_image_free(img);
|
||||
|
||||
message = FileDialog::Instance()->GetCurrentFileName() + "(" + std::to_string(w) + "x" + std::to_string(h) + ")";
|
||||
}
|
||||
else {
|
||||
// clear image
|
||||
unsigned char clearColor[4] = {0};
|
||||
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 1, 1, 0, GL_RGBA, GL_UNSIGNED_BYTE, clearColor);
|
||||
|
||||
message = "Please select an image (" + vFilter + ").";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// draw text and image
|
||||
ImGui::TextColored(ImVec4(0, 1, 1, 1), "%s", message.c_str());
|
||||
ImGui::Image((void*)(intptr_t)tex, image_size);
|
||||
|
||||
// release Ok button if image is not null
|
||||
if (vCantContinue)
|
||||
*vCantContinue = img!=NULL;
|
||||
}
|
||||
|
||||
void UserInterface::OpenFileText()
|
||||
{
|
||||
currentFileDialog = "ChooseFileText";
|
||||
|
||||
FileDialog::Instance()->ClearFilterColor();
|
||||
FileDialog::Instance()->SetFilterColor(".cpp", ImVec4(1,1,0,0.5));
|
||||
FileDialog::Instance()->SetFilterColor(".h", ImVec4(0,1,0,0.5));
|
||||
FileDialog::Instance()->SetFilterColor(".hpp", ImVec4(0,0,1,0.5));
|
||||
|
||||
FileDialog::Instance()->OpenDialog(currentFileDialog.c_str(), "Open Text File", ".cpp\0.h\0.hpp\0\0", ".", "",
|
||||
std::bind(&TextInfosPane, std::placeholders::_1, std::placeholders::_2), 350, "Text info");
|
||||
|
||||
}
|
||||
|
||||
void UserInterface::OpenFileImage()
|
||||
{
|
||||
currentFileDialog = "ChooseFileImage";
|
||||
|
||||
FileDialog::Instance()->ClearFilterColor();
|
||||
FileDialog::Instance()->SetFilterColor(".png", ImVec4(0,1,1,1.0));
|
||||
FileDialog::Instance()->SetFilterColor(".jpg", ImVec4(0,1,1,1.0));
|
||||
FileDialog::Instance()->SetFilterColor(".gif", ImVec4(0,1,1,1.0));
|
||||
|
||||
FileDialog::Instance()->OpenDialog(currentFileDialog.c_str(), "Open Image File", ".*\0.png\0.jpg\0.gif\0\0", ".", "",
|
||||
std::bind(&ImageInfosPane, std::placeholders::_1, std::placeholders::_2), 350, "Image info");
|
||||
|
||||
}
|
||||
|
||||
void UserInterface::OpenFileMedia()
|
||||
{
|
||||
currentFileDialog = "ChooseFileMedia";
|
||||
|
||||
FileDialog::Instance()->ClearFilterColor();
|
||||
FileDialog::Instance()->SetFilterColor(".mp4", ImVec4(0,1,1,1.0));
|
||||
FileDialog::Instance()->SetFilterColor(".avi", ImVec4(0,1,1,1.0));
|
||||
FileDialog::Instance()->SetFilterColor(".mov", ImVec4(0,1,1,1.0));
|
||||
|
||||
FileDialog::Instance()->OpenDialog(currentFileDialog.c_str(), "Open Media File", ".*\0.mp4\0.avi\0.mov\0\0", ".", "", "Media");
|
||||
|
||||
}
|
||||
|
||||
|
||||
MainWindow::MainWindow()
|
||||
{
|
||||
show_overlay_stats = false;
|
||||
show_app_about = false;
|
||||
show_demo_window = false;
|
||||
show_logs_window = false;
|
||||
show_icons_window = false;
|
||||
show_editor_window = false;
|
||||
screenshot_step = 0;
|
||||
}
|
||||
|
||||
void MainWindow::ShowStats(bool* p_open)
|
||||
{
|
||||
const float DISTANCE = 10.0f;
|
||||
static int corner = 1;
|
||||
ImGuiIO& io = ImGui::GetIO();
|
||||
if (corner != -1)
|
||||
{
|
||||
ImVec2 window_pos = ImVec2((corner & 1) ? io.DisplaySize.x - DISTANCE : DISTANCE, (corner & 2) ? io.DisplaySize.y - DISTANCE : DISTANCE);
|
||||
ImVec2 window_pos_pivot = ImVec2((corner & 1) ? 1.0f : 0.0f, (corner & 2) ? 1.0f : 0.0f);
|
||||
ImGui::SetNextWindowPos(window_pos, ImGuiCond_Always, window_pos_pivot);
|
||||
}
|
||||
|
||||
ImGui::SetNextWindowBgAlpha(0.35f); // Transparent background
|
||||
|
||||
if (ImGui::Begin("v-mix statistics", NULL, (corner != -1 ? ImGuiWindowFlags_NoMove : 0) | ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_NoFocusOnAppearing | ImGuiWindowFlags_NoNav))
|
||||
{
|
||||
|
||||
ImGuiToolkit::PushFont(ImGuiToolkit::FONT_MONO);
|
||||
if (ImGui::IsMousePosValid())
|
||||
ImGui::Text("Mouse (%.1f,%.1f)", io.MousePos.x, io.MousePos.y);
|
||||
else
|
||||
ImGui::Text("Mouse <invalid>");
|
||||
|
||||
ImGui::Text("Rendering %.1f FPS", io.Framerate);
|
||||
ImGui::PopFont();
|
||||
|
||||
if (ImGui::BeginPopupContextWindow())
|
||||
{
|
||||
if (ImGui::MenuItem("Custom", NULL, corner == -1)) corner = -1;
|
||||
if (ImGui::MenuItem("Top-left", NULL, corner == 0)) corner = 0;
|
||||
if (ImGui::MenuItem("Top-right", NULL, corner == 1)) corner = 1;
|
||||
if (ImGui::MenuItem("Bottom-left", NULL, corner == 2)) corner = 2;
|
||||
if (ImGui::MenuItem("Bottom-right", NULL, corner == 3)) corner = 3;
|
||||
if (p_open && ImGui::MenuItem("Close")) *p_open = false;
|
||||
ImGui::EndPopup();
|
||||
}
|
||||
}
|
||||
ImGui::End();
|
||||
}
|
||||
|
||||
void MainWindow::ToggleLogs()
|
||||
{
|
||||
show_logs_window = !show_logs_window;
|
||||
}
|
||||
|
||||
void MainWindow::ToggleStats()
|
||||
{
|
||||
show_overlay_stats = !show_overlay_stats;
|
||||
}
|
||||
|
||||
void MainWindow::StartScreenshot()
|
||||
{
|
||||
screenshot_step = 1;
|
||||
}
|
||||
|
||||
void MainWindow::Render()
|
||||
{
|
||||
// We specify a default position/size in case there's no data in the .ini file. Typically this isn't required! We only do it to make the Demo applications a little more welcoming.
|
||||
ImGui::SetNextWindowPos(ImVec2(40, 40), ImGuiCond_FirstUseEver);
|
||||
ImGui::SetNextWindowSize(ImVec2(300, 300), ImGuiCond_FirstUseEver);
|
||||
|
||||
|
||||
ImGui::Begin( ICON_FA_DOT_CIRCLE " v-mix", NULL, ImGuiWindowFlags_MenuBar | ImGuiWindowFlags_NoCollapse);
|
||||
|
||||
// Menu Bar
|
||||
if (ImGui::BeginMenuBar())
|
||||
{
|
||||
if (ImGui::BeginMenu("File"))
|
||||
{
|
||||
if (ImGui::MenuItem( ICON_FA_FILE " New")) {
|
||||
|
||||
}
|
||||
if (ImGui::MenuItem( ICON_FA_FILE_UPLOAD " Open", "Ctrl+O")) {
|
||||
UserInterface::OpenFileMedia();
|
||||
}
|
||||
if (ImGui::MenuItem( ICON_FA_POWER_OFF " Quit", "Ctrl+Q")) {
|
||||
Rendering::Close();
|
||||
}
|
||||
ImGui::EndMenu();
|
||||
}
|
||||
if (ImGui::BeginMenu("Appearance"))
|
||||
{
|
||||
ImGui::SetNextItemWidth(200);
|
||||
if ( ImGui::SliderFloat("Scale", &Settings::application.scale, 0.8f, 1.2f, "%.1f"))
|
||||
ImGui::GetIO().FontGlobalScale = Settings::application.scale;
|
||||
|
||||
ImGui::SetNextItemWidth(200);
|
||||
if ( ImGui::Combo("Color", &Settings::application.color, "Blue\0Orange\0Grey\0\0"))
|
||||
ImGuiToolkit::SetAccentColor(static_cast<ImGuiToolkit::accent_color>(Settings::application.color));
|
||||
ImGui::EndMenu();
|
||||
}
|
||||
if (ImGui::BeginMenu("Tools"))
|
||||
{
|
||||
ImGui::MenuItem( ICON_FA_CODE " Shader Editor", NULL, &show_editor_window);
|
||||
ImGui::MenuItem( ICON_FA_LIST " Logs", "Ctrl+L", &show_logs_window);
|
||||
ImGui::MenuItem( ICON_FA_TACHOMETER_ALT " Metrics", NULL, &show_overlay_stats);
|
||||
if ( ImGui::MenuItem( ICON_FA_CAMERA_RETRO " Screenshot", NULL) )
|
||||
StartScreenshot();
|
||||
|
||||
ImGui::MenuItem("--", NULL, false, false);
|
||||
ImGui::MenuItem("Icons", NULL, &show_icons_window);
|
||||
ImGui::MenuItem("Demo ImGui", NULL, &show_demo_window);
|
||||
ImGui::MenuItem("About ImGui", NULL, &show_app_about);
|
||||
if ( ImGui::MenuItem("About Gstreamer", NULL) )
|
||||
OpenWebpage("https://gstreamer.freedesktop.org/");
|
||||
ImGui::EndMenu();
|
||||
}
|
||||
ImGui::EndMenuBar();
|
||||
}
|
||||
|
||||
// content
|
||||
ImGuiToolkit::Icon(7, 1);
|
||||
ImGui::SameLine(0, 10);
|
||||
|
||||
ImGuiToolkit::PushFont(ImGuiToolkit::FONT_ITALIC);
|
||||
ImGui::Text("This is some useful text."); // Display some text (you can use a format strings too)
|
||||
ImGui::PopFont();
|
||||
|
||||
if( ImGui::Button( ICON_FA_FOLDER_OPEN " Open File" ) && !loadThread.joinable())
|
||||
{
|
||||
loadThreadDone = false;
|
||||
loadThread = std::thread(NativeOpenFile, "hello");
|
||||
}
|
||||
|
||||
if( loadThreadDone && loadThread.joinable() ) {
|
||||
loadThread.join();
|
||||
|
||||
// TODO : deal with filename
|
||||
}
|
||||
|
||||
ImGui::End(); // "v-mix"
|
||||
|
||||
if (show_logs_window)
|
||||
Log::ShowLogWindow(&show_logs_window);
|
||||
if (show_overlay_stats)
|
||||
ShowStats(&show_overlay_stats);
|
||||
if (show_editor_window)
|
||||
drawTextEditor(&show_editor_window);
|
||||
if (show_icons_window)
|
||||
ImGuiToolkit::ShowIconsWindow(&show_icons_window);
|
||||
if (show_demo_window)
|
||||
ImGui::ShowDemoWindow(&show_demo_window);
|
||||
if (show_app_about)
|
||||
ImGui::ShowAboutWindow(&show_app_about);
|
||||
|
||||
// taking screenshot is in 3 steps
|
||||
// 1) wait 1 frame that the menu / action showing button to take screenshot disapears
|
||||
// 2) wait 1 frame that rendering manager takes the actual screenshot
|
||||
// 3) if rendering manager current screenshot is ok, save it
|
||||
if (screenshot_step > 0) {
|
||||
|
||||
switch(screenshot_step) {
|
||||
case 1:
|
||||
screenshot_step = 2;
|
||||
break;
|
||||
case 2:
|
||||
Rendering::RequestScreenshot();
|
||||
screenshot_step = 3;
|
||||
break;
|
||||
case 3:
|
||||
{
|
||||
if ( Rendering::CurrentScreenshot()->IsFull() ){
|
||||
std::time_t t = std::time(0); // get time now
|
||||
std::tm* now = std::localtime(&t);
|
||||
std::string filename = ImGuiToolkit::DateTime() + "_vmixcapture.png";
|
||||
Rendering::CurrentScreenshot()->SaveFile( filename.c_str() );
|
||||
Rendering::CurrentScreenshot()->Clear();
|
||||
}
|
||||
screenshot_step = 4;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
screenshot_step = 0;
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
void UserInterface::OpenTextEditor(std::string text)
|
||||
{
|
||||
currentTextEdit = text;
|
||||
|
||||
auto lang = TextEditor::LanguageDefinition::GLSL();
|
||||
editor.SetLanguageDefinition(lang);
|
||||
|
||||
editor.SetText(currentTextEdit);
|
||||
}
|
||||
|
||||
void MainWindow::drawTextEditor(bool* p_open)
|
||||
{
|
||||
static bool show_statusbar = true;
|
||||
ImGui::Begin( ICON_FA_CODE " Shader Editor", p_open, ImGuiWindowFlags_HorizontalScrollbar | ImGuiWindowFlags_MenuBar);
|
||||
ImGui::SetWindowSize(ImVec2(800, 600), ImGuiCond_FirstUseEver);
|
||||
if (ImGui::BeginMenuBar())
|
||||
{
|
||||
if (ImGui::BeginMenu("Edit"))
|
||||
{
|
||||
bool ro = editor.IsReadOnly();
|
||||
if (ImGui::MenuItem("Read-only mode", nullptr, &ro))
|
||||
editor.SetReadOnly(ro);
|
||||
ImGui::Separator();
|
||||
|
||||
if (ImGui::MenuItem( ICON_FA_UNDO " Undo", "Ctrl-Z", nullptr, !ro && editor.CanUndo()))
|
||||
editor.Undo();
|
||||
if (ImGui::MenuItem( ICON_FA_REDO " Redo", "Ctrl-Y", nullptr, !ro && editor.CanRedo()))
|
||||
editor.Redo();
|
||||
|
||||
ImGui::Separator();
|
||||
|
||||
if (ImGui::MenuItem( ICON_FA_COPY " Copy", "Ctrl-C", nullptr, editor.HasSelection()))
|
||||
editor.Copy();
|
||||
if (ImGui::MenuItem( ICON_FA_CUT " Cut", "Ctrl-X", nullptr, !ro && editor.HasSelection()))
|
||||
editor.Cut();
|
||||
if (ImGui::MenuItem( ICON_FA_ERASER " Delete", "Del", nullptr, !ro && editor.HasSelection()))
|
||||
editor.Delete();
|
||||
if (ImGui::MenuItem( ICON_FA_PASTE " Paste", "Ctrl-V", nullptr, !ro && ImGui::GetClipboardText() != nullptr))
|
||||
editor.Paste();
|
||||
|
||||
ImGui::Separator();
|
||||
|
||||
if (ImGui::MenuItem( "Select all", nullptr, nullptr))
|
||||
editor.SetSelection(TextEditor::Coordinates(), TextEditor::Coordinates(editor.GetTotalLines(), 0));
|
||||
|
||||
ImGui::EndMenu();
|
||||
}
|
||||
|
||||
if (ImGui::BeginMenu("View"))
|
||||
{
|
||||
bool ws = editor.IsShowingWhitespaces();
|
||||
if (ImGui::MenuItem( ICON_FA_LONG_ARROW_ALT_RIGHT " Whitespace", nullptr, &ws))
|
||||
editor.SetShowWhitespaces(ws);
|
||||
ImGui::MenuItem( ICON_FA_WINDOW_MAXIMIZE " Statusbar", nullptr, &show_statusbar);
|
||||
ImGui::EndMenu();
|
||||
}
|
||||
ImGui::EndMenuBar();
|
||||
}
|
||||
|
||||
if (show_statusbar) {
|
||||
auto cpos = editor.GetCursorPosition();
|
||||
ImGui::Text("%6d/%-6d %6d lines | %s | %s | %s ", cpos.mLine + 1, cpos.mColumn + 1, editor.GetTotalLines(),
|
||||
editor.IsOverwrite() ? "Ovr" : "Ins",
|
||||
editor.CanUndo() ? "*" : " ",
|
||||
editor.GetLanguageDefinition().mName.c_str());
|
||||
}
|
||||
|
||||
ImGuiToolkit::PushFont(ImGuiToolkit::FONT_MONO);
|
||||
editor.Render("TextEditor");
|
||||
ImGui::PopFont();
|
||||
|
||||
ImGui::End();
|
||||
|
||||
}
|
||||
|
||||
78
UserInterfaceManager.h
Normal file
78
UserInterfaceManager.h
Normal file
@@ -0,0 +1,78 @@
|
||||
#ifndef __UI_MANAGER_H_
|
||||
#define __UI_MANAGER_H_
|
||||
|
||||
#include <string>
|
||||
#include <list>
|
||||
using namespace std;
|
||||
|
||||
class MessageBox
|
||||
{
|
||||
public:
|
||||
list<string> messages;
|
||||
|
||||
MessageBox();
|
||||
void Append(const string& text);
|
||||
void Render(float width);
|
||||
};
|
||||
|
||||
class MainWindow
|
||||
{
|
||||
bool show_overlay_stats;
|
||||
bool show_app_about;
|
||||
bool show_demo_window;
|
||||
bool show_logs_window;
|
||||
bool show_icons_window;
|
||||
bool show_editor_window;
|
||||
unsigned int screenshot_step;
|
||||
|
||||
void ShowStats(bool* p_open);
|
||||
void drawTextEditor(bool* p_open);
|
||||
|
||||
public:
|
||||
MainWindow();
|
||||
|
||||
void ToggleLogs();
|
||||
void ToggleStats();
|
||||
void StartScreenshot();
|
||||
void Render();
|
||||
};
|
||||
|
||||
class UserInterface
|
||||
{
|
||||
|
||||
// own implementation of ImGui
|
||||
static unsigned int textureicons;
|
||||
static MainWindow mainwindow;
|
||||
static MessageBox warnings;
|
||||
static std::string currentFileDialog;
|
||||
static std::string currentTextEdit;
|
||||
|
||||
static void handleKeyboard();
|
||||
static void handleMouse();
|
||||
|
||||
public:
|
||||
|
||||
// pre-loop initialization
|
||||
static bool Init();
|
||||
// loop update start new frame
|
||||
static void NewFrame();
|
||||
// loop update rendering
|
||||
static void Render();
|
||||
// Post-loop termination
|
||||
static void Terminate();
|
||||
// Boxer integration in OS of Error messages
|
||||
static void Error(const string& text, const string& title = std::string());
|
||||
// ImGui modal dialog for Warning messages
|
||||
static void Warning(const char* fmt, ...);
|
||||
// Open an Open File dialog for TEXT file
|
||||
static void OpenFileText();
|
||||
// Open an Open File dialog for IMAGE file
|
||||
static void OpenFileImage();
|
||||
// Open an Open File dialog for MEDIA file
|
||||
static void OpenFileMedia();
|
||||
|
||||
static void OpenTextEditor(std::string text);
|
||||
};
|
||||
|
||||
#endif /* #define __UI_MANAGER_H_ */
|
||||
|
||||
617
cmake/modules/CMakeRC.cmake
Normal file
617
cmake/modules/CMakeRC.cmake
Normal file
@@ -0,0 +1,617 @@
|
||||
# This block is executed when generating an intermediate resource file, not when
|
||||
# running in CMake configure mode
|
||||
if(_CMRC_GENERATE_MODE)
|
||||
# Read in the digits
|
||||
file(READ "${INPUT_FILE}" bytes HEX)
|
||||
# Format each pair into a character literal. Heuristics seem to favor doing
|
||||
# the conversion in groups of five for fastest conversion
|
||||
string(REGEX REPLACE "(..)(..)(..)(..)(..)" "'\\\\x\\1','\\\\x\\2','\\\\x\\3','\\\\x\\4','\\\\x\\5'," chars "${bytes}")
|
||||
# Since we did this in groups, we have some leftovers to clean up
|
||||
string(LENGTH "${bytes}" n_bytes2)
|
||||
math(EXPR n_bytes "${n_bytes2} / 2")
|
||||
math(EXPR remainder "${n_bytes} % 5") # <-- '5' is the grouping count from above
|
||||
set(cleanup_re "$")
|
||||
set(cleanup_sub )
|
||||
while(remainder)
|
||||
set(cleanup_re "(..)${cleanup_re}")
|
||||
set(cleanup_sub "'\\\\x\\${remainder}',${cleanup_sub}")
|
||||
math(EXPR remainder "${remainder} - 1")
|
||||
endwhile()
|
||||
if(NOT cleanup_re STREQUAL "$")
|
||||
string(REGEX REPLACE "${cleanup_re}" "${cleanup_sub}" chars "${chars}")
|
||||
endif()
|
||||
string(CONFIGURE [[
|
||||
namespace { const char file_array[] = { @chars@ 0 }; }
|
||||
namespace cmrc { namespace @NAMESPACE@ { namespace res_chars {
|
||||
extern const char* const @SYMBOL@_begin = file_array;
|
||||
extern const char* const @SYMBOL@_end = file_array + @n_bytes@;
|
||||
}}}
|
||||
]] code)
|
||||
file(WRITE "${OUTPUT_FILE}" "${code}")
|
||||
# Exit from the script. Nothing else needs to be processed
|
||||
return()
|
||||
endif()
|
||||
|
||||
set(_version 2.0.0)
|
||||
|
||||
cmake_minimum_required(VERSION 3.3)
|
||||
include(CMakeParseArguments)
|
||||
|
||||
if(COMMAND cmrc_add_resource_library)
|
||||
if(NOT DEFINED _CMRC_VERSION OR NOT (_version STREQUAL _CMRC_VERSION))
|
||||
message(WARNING "More than one CMakeRC version has been included in this project.")
|
||||
endif()
|
||||
# CMakeRC has already been included! Don't do anything
|
||||
return()
|
||||
endif()
|
||||
|
||||
set(_CMRC_VERSION "${_version}" CACHE INTERNAL "CMakeRC version. Used for checking for conflicts")
|
||||
|
||||
set(_CMRC_SCRIPT "${CMAKE_CURRENT_LIST_FILE}" CACHE INTERNAL "Path to CMakeRC script")
|
||||
|
||||
function(_cmrc_normalize_path var)
|
||||
set(path "${${var}}")
|
||||
file(TO_CMAKE_PATH "${path}" path)
|
||||
while(path MATCHES "//")
|
||||
string(REPLACE "//" "/" path "${path}")
|
||||
endwhile()
|
||||
string(REGEX REPLACE "/+$" "" path "${path}")
|
||||
set("${var}" "${path}" PARENT_SCOPE)
|
||||
endfunction()
|
||||
|
||||
get_filename_component(_inc_dir "${CMAKE_BINARY_DIR}/_cmrc/include" ABSOLUTE)
|
||||
set(CMRC_INCLUDE_DIR "${_inc_dir}" CACHE INTERNAL "Directory for CMakeRC include files")
|
||||
# Let's generate the primary include file
|
||||
file(MAKE_DIRECTORY "${CMRC_INCLUDE_DIR}/cmrc")
|
||||
set(hpp_content [==[
|
||||
#ifndef CMRC_CMRC_HPP_INCLUDED
|
||||
#define CMRC_CMRC_HPP_INCLUDED
|
||||
|
||||
#include <cassert>
|
||||
#include <functional>
|
||||
#include <iterator>
|
||||
#include <list>
|
||||
#include <map>
|
||||
#include <mutex>
|
||||
#include <string>
|
||||
#include <system_error>
|
||||
#include <type_traits>
|
||||
|
||||
namespace cmrc { namespace detail { struct dummy; } }
|
||||
|
||||
#define CMRC_DECLARE(libid) \
|
||||
namespace cmrc { namespace detail { \
|
||||
struct dummy; \
|
||||
static_assert(std::is_same<dummy, ::cmrc::detail::dummy>::value, "CMRC_DECLARE() must only appear at the global namespace"); \
|
||||
} } \
|
||||
namespace cmrc { namespace libid { \
|
||||
cmrc::embedded_filesystem get_filesystem(); \
|
||||
} } static_assert(true, "")
|
||||
|
||||
namespace cmrc {
|
||||
|
||||
class file {
|
||||
const char* _begin = nullptr;
|
||||
const char* _end = nullptr;
|
||||
|
||||
public:
|
||||
using iterator = const char*;
|
||||
using const_iterator = iterator;
|
||||
iterator begin() const noexcept { return _begin; }
|
||||
iterator cbegin() const noexcept { return _begin; }
|
||||
iterator end() const noexcept { return _end; }
|
||||
iterator cend() const noexcept { return _end; }
|
||||
std::size_t size() const { return std::distance(begin(), end()); }
|
||||
|
||||
file() = default;
|
||||
file(iterator beg, iterator end) noexcept : _begin(beg), _end(end) {}
|
||||
};
|
||||
|
||||
class directory_entry;
|
||||
|
||||
namespace detail {
|
||||
|
||||
class directory;
|
||||
class file_data;
|
||||
|
||||
class file_or_directory {
|
||||
union _data_t {
|
||||
class file_data* file_data;
|
||||
class directory* directory;
|
||||
} _data;
|
||||
bool _is_file = true;
|
||||
|
||||
public:
|
||||
explicit file_or_directory(file_data& f) {
|
||||
_data.file_data = &f;
|
||||
}
|
||||
explicit file_or_directory(directory& d) {
|
||||
_data.directory = &d;
|
||||
_is_file = false;
|
||||
}
|
||||
bool is_file() const noexcept {
|
||||
return _is_file;
|
||||
}
|
||||
bool is_directory() const noexcept {
|
||||
return !is_file();
|
||||
}
|
||||
const directory& as_directory() const noexcept {
|
||||
assert(!is_file());
|
||||
return *_data.directory;
|
||||
}
|
||||
const file_data& as_file() const noexcept {
|
||||
assert(is_file());
|
||||
return *_data.file_data;
|
||||
}
|
||||
};
|
||||
|
||||
class file_data {
|
||||
public:
|
||||
const char* begin_ptr;
|
||||
const char* end_ptr;
|
||||
file_data(const file_data&) = delete;
|
||||
file_data(const char* b, const char* e) : begin_ptr(b), end_ptr(e) {}
|
||||
};
|
||||
|
||||
inline std::pair<std::string, std::string> split_path(const std::string& path) {
|
||||
auto first_sep = path.find("/");
|
||||
if (first_sep == path.npos) {
|
||||
return std::make_pair(path, "");
|
||||
} else {
|
||||
return std::make_pair(path.substr(0, first_sep), path.substr(first_sep + 1));
|
||||
}
|
||||
}
|
||||
|
||||
struct created_subdirectory {
|
||||
class directory& directory;
|
||||
class file_or_directory& index_entry;
|
||||
};
|
||||
|
||||
class directory {
|
||||
std::list<file_data> _files;
|
||||
std::list<directory> _dirs;
|
||||
std::map<std::string, file_or_directory> _index;
|
||||
|
||||
using base_iterator = std::map<std::string, file_or_directory>::const_iterator;
|
||||
|
||||
public:
|
||||
|
||||
directory() = default;
|
||||
directory(const directory&) = delete;
|
||||
|
||||
created_subdirectory add_subdir(std::string name) & {
|
||||
_dirs.emplace_back();
|
||||
auto& back = _dirs.back();
|
||||
auto& fod = _index.emplace(name, file_or_directory{back}).first->second;
|
||||
return created_subdirectory{back, fod};
|
||||
}
|
||||
|
||||
file_or_directory* add_file(std::string name, const char* begin, const char* end) & {
|
||||
assert(_index.find(name) == _index.end());
|
||||
_files.emplace_back(begin, end);
|
||||
return &_index.emplace(name, file_or_directory{_files.back()}).first->second;
|
||||
}
|
||||
|
||||
const file_or_directory* get(const std::string& path) const {
|
||||
auto pair = split_path(path);
|
||||
auto child = _index.find(pair.first);
|
||||
if (child == _index.end()) {
|
||||
return nullptr;
|
||||
}
|
||||
auto& entry = child->second;
|
||||
if (pair.second.empty()) {
|
||||
// We're at the end of the path
|
||||
return &entry;
|
||||
}
|
||||
|
||||
if (entry.is_file()) {
|
||||
// We can't traverse into a file. Stop.
|
||||
return nullptr;
|
||||
}
|
||||
// Keep going down
|
||||
return entry.as_directory().get(pair.second);
|
||||
}
|
||||
|
||||
class iterator {
|
||||
base_iterator _base_iter;
|
||||
base_iterator _end_iter;
|
||||
public:
|
||||
using value_type = directory_entry;
|
||||
using difference_type = std::ptrdiff_t;
|
||||
using pointer = const value_type*;
|
||||
using reference = const value_type&;
|
||||
using iterator_category = std::input_iterator_tag;
|
||||
|
||||
iterator() = default;
|
||||
explicit iterator(base_iterator iter, base_iterator end) : _base_iter(iter), _end_iter(end) {}
|
||||
|
||||
iterator begin() const noexcept {
|
||||
return *this;
|
||||
}
|
||||
|
||||
iterator end() const noexcept {
|
||||
return iterator(_end_iter, _end_iter);
|
||||
}
|
||||
|
||||
inline value_type operator*() const noexcept;
|
||||
|
||||
bool operator==(const iterator& rhs) const noexcept {
|
||||
return _base_iter == rhs._base_iter;
|
||||
}
|
||||
|
||||
bool operator!=(const iterator& rhs) const noexcept {
|
||||
return !(*this == rhs);
|
||||
}
|
||||
|
||||
iterator operator++() noexcept {
|
||||
auto cp = *this;
|
||||
++_base_iter;
|
||||
return cp;
|
||||
}
|
||||
|
||||
iterator& operator++(int) noexcept {
|
||||
++_base_iter;
|
||||
return *this;
|
||||
}
|
||||
};
|
||||
|
||||
using const_iterator = iterator;
|
||||
|
||||
iterator begin() const noexcept {
|
||||
return iterator(_index.begin(), _index.end());
|
||||
}
|
||||
|
||||
iterator end() const noexcept {
|
||||
return iterator();
|
||||
}
|
||||
};
|
||||
|
||||
inline std::string normalize_path(std::string path) {
|
||||
while (path.find("/") == 0) {
|
||||
path.erase(path.begin());
|
||||
}
|
||||
while (!path.empty() && (path.rfind("/") == path.size() - 1)) {
|
||||
path.pop_back();
|
||||
}
|
||||
auto off = path.npos;
|
||||
while ((off = path.find("//")) != path.npos) {
|
||||
path.erase(path.begin() + off);
|
||||
}
|
||||
return path;
|
||||
}
|
||||
|
||||
using index_type = std::map<std::string, const cmrc::detail::file_or_directory*>;
|
||||
|
||||
} // detail
|
||||
|
||||
class directory_entry {
|
||||
std::string _fname;
|
||||
const detail::file_or_directory* _item;
|
||||
|
||||
public:
|
||||
directory_entry() = delete;
|
||||
explicit directory_entry(std::string filename, const detail::file_or_directory& item)
|
||||
: _fname(filename)
|
||||
, _item(&item)
|
||||
{}
|
||||
|
||||
const std::string& filename() const & {
|
||||
return _fname;
|
||||
}
|
||||
std::string filename() const && {
|
||||
return std::move(_fname);
|
||||
}
|
||||
|
||||
bool is_file() const {
|
||||
return _item->is_file();
|
||||
}
|
||||
|
||||
bool is_directory() const {
|
||||
return _item->is_directory();
|
||||
}
|
||||
};
|
||||
|
||||
directory_entry detail::directory::iterator::operator*() const noexcept {
|
||||
assert(begin() != end());
|
||||
return directory_entry(_base_iter->first, _base_iter->second);
|
||||
}
|
||||
|
||||
using directory_iterator = detail::directory::iterator;
|
||||
|
||||
class embedded_filesystem {
|
||||
// Never-null:
|
||||
const cmrc::detail::index_type* _index;
|
||||
const detail::file_or_directory* _get(std::string path) const {
|
||||
path = detail::normalize_path(path);
|
||||
auto found = _index->find(path);
|
||||
if (found == _index->end()) {
|
||||
return nullptr;
|
||||
} else {
|
||||
return found->second;
|
||||
}
|
||||
}
|
||||
|
||||
public:
|
||||
explicit embedded_filesystem(const detail::index_type& index)
|
||||
: _index(&index)
|
||||
{}
|
||||
|
||||
file open(const std::string& path) const {
|
||||
auto entry_ptr = _get(path);
|
||||
if (!entry_ptr || !entry_ptr->is_file()) {
|
||||
throw std::system_error(make_error_code(std::errc::no_such_file_or_directory), path);
|
||||
}
|
||||
auto& dat = entry_ptr->as_file();
|
||||
return file{dat.begin_ptr, dat.end_ptr};
|
||||
}
|
||||
|
||||
bool is_file(const std::string& path) const noexcept {
|
||||
auto entry_ptr = _get(path);
|
||||
return entry_ptr && entry_ptr->is_file();
|
||||
}
|
||||
|
||||
bool is_directory(const std::string& path) const noexcept {
|
||||
auto entry_ptr = _get(path);
|
||||
return entry_ptr && entry_ptr->is_directory();
|
||||
}
|
||||
|
||||
bool exists(const std::string& path) const noexcept {
|
||||
return !!_get(path);
|
||||
}
|
||||
|
||||
directory_iterator iterate_directory(const std::string& path) const {
|
||||
auto entry_ptr = _get(path);
|
||||
if (!entry_ptr) {
|
||||
throw std::system_error(make_error_code(std::errc::no_such_file_or_directory), path);
|
||||
}
|
||||
if (!entry_ptr->is_directory()) {
|
||||
throw std::system_error(make_error_code(std::errc::not_a_directory), path);
|
||||
}
|
||||
return entry_ptr->as_directory().begin();
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // CMRC_CMRC_HPP_INCLUDED
|
||||
]==])
|
||||
|
||||
set(cmrc_hpp "${CMRC_INCLUDE_DIR}/cmrc/cmrc.hpp" CACHE INTERNAL "")
|
||||
set(_generate 1)
|
||||
if(EXISTS "${cmrc_hpp}")
|
||||
file(READ "${cmrc_hpp}" _current)
|
||||
if(_current STREQUAL hpp_content)
|
||||
set(_generate 0)
|
||||
endif()
|
||||
endif()
|
||||
file(GENERATE OUTPUT "${cmrc_hpp}" CONTENT "${hpp_content}" CONDITION ${_generate})
|
||||
|
||||
add_library(cmrc-base INTERFACE)
|
||||
target_include_directories(cmrc-base INTERFACE "${CMRC_INCLUDE_DIR}")
|
||||
# Signal a basic C++11 feature to require C++11.
|
||||
target_compile_features(cmrc-base INTERFACE cxx_nullptr)
|
||||
set_property(TARGET cmrc-base PROPERTY INTERFACE_CXX_EXTENSIONS OFF)
|
||||
add_library(cmrc::base ALIAS cmrc-base)
|
||||
|
||||
function(cmrc_add_resource_library name)
|
||||
set(args ALIAS NAMESPACE)
|
||||
cmake_parse_arguments(ARG "" "${args}" "" "${ARGN}")
|
||||
# Generate the identifier for the resource library's namespace
|
||||
set(ns_re "[a-zA-Z_][a-zA-Z0-9_]*")
|
||||
if(NOT DEFINED ARG_NAMESPACE)
|
||||
# Check that the library name is also a valid namespace
|
||||
if(NOT name MATCHES "${ns_re}")
|
||||
message(SEND_ERROR "Library name is not a valid namespace. Specify the NAMESPACE argument")
|
||||
endif()
|
||||
set(ARG_NAMESPACE "${name}")
|
||||
else()
|
||||
if(NOT ARG_NAMESPACE MATCHES "${ns_re}")
|
||||
message(SEND_ERROR "NAMESPACE for ${name} is not a valid C++ namespace identifier (${ARG_NAMESPACE})")
|
||||
endif()
|
||||
endif()
|
||||
set(libname "${name}")
|
||||
# Generate a library with the compiled in character arrays.
|
||||
string(CONFIGURE [=[
|
||||
#include <cmrc/cmrc.hpp>
|
||||
#include <map>
|
||||
#include <utility>
|
||||
|
||||
namespace cmrc {
|
||||
namespace @ARG_NAMESPACE@ {
|
||||
|
||||
namespace res_chars {
|
||||
// These are the files which are available in this resource library
|
||||
$<JOIN:$<TARGET_PROPERTY:@libname@,CMRC_EXTERN_DECLS>,
|
||||
>
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
const cmrc::detail::index_type&
|
||||
get_root_index() {
|
||||
static cmrc::detail::directory root_directory_;
|
||||
static cmrc::detail::file_or_directory root_directory_fod{root_directory_};
|
||||
static cmrc::detail::index_type root_index;
|
||||
root_index.emplace("", &root_directory_fod);
|
||||
struct dir_inl {
|
||||
class cmrc::detail::directory& directory;
|
||||
};
|
||||
dir_inl root_directory_dir{root_directory_};
|
||||
(void)root_directory_dir;
|
||||
$<JOIN:$<TARGET_PROPERTY:@libname@,CMRC_MAKE_DIRS>,
|
||||
>
|
||||
$<JOIN:$<TARGET_PROPERTY:@libname@,CMRC_MAKE_FILES>,
|
||||
>
|
||||
return root_index;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
cmrc::embedded_filesystem get_filesystem() {
|
||||
static auto& index = get_root_index();
|
||||
return cmrc::embedded_filesystem{index};
|
||||
}
|
||||
|
||||
} // @ARG_NAMESPACE@
|
||||
} // cmrc
|
||||
]=] cpp_content @ONLY)
|
||||
get_filename_component(libdir "${CMAKE_CURRENT_BINARY_DIR}/__cmrc_${name}" ABSOLUTE)
|
||||
get_filename_component(lib_tmp_cpp "${libdir}/lib_.cpp" ABSOLUTE)
|
||||
string(REPLACE "\n " "\n" cpp_content "${cpp_content}")
|
||||
file(GENERATE OUTPUT "${lib_tmp_cpp}" CONTENT "${cpp_content}")
|
||||
get_filename_component(libcpp "${libdir}/lib.cpp" ABSOLUTE)
|
||||
add_custom_command(OUTPUT "${libcpp}"
|
||||
DEPENDS "${lib_tmp_cpp}" "${cmrc_hpp}"
|
||||
COMMAND ${CMAKE_COMMAND} -E copy_if_different "${lib_tmp_cpp}" "${libcpp}"
|
||||
COMMENT "Generating ${name} resource loader"
|
||||
)
|
||||
# Generate the actual static library. Each source file is just a single file
|
||||
# with a character array compiled in containing the contents of the
|
||||
# corresponding resource file.
|
||||
add_library(${name} STATIC ${libcpp})
|
||||
set_property(TARGET ${name} PROPERTY CMRC_LIBDIR "${libdir}")
|
||||
set_property(TARGET ${name} PROPERTY CMRC_NAMESPACE "${ARG_NAMESPACE}")
|
||||
target_link_libraries(${name} PUBLIC cmrc::base)
|
||||
set_property(TARGET ${name} PROPERTY CMRC_IS_RESOURCE_LIBRARY TRUE)
|
||||
if(ARG_ALIAS)
|
||||
add_library("${ARG_ALIAS}" ALIAS ${name})
|
||||
endif()
|
||||
cmrc_add_resources(${name} ${ARG_UNPARSED_ARGUMENTS})
|
||||
endfunction()
|
||||
|
||||
function(_cmrc_register_dirs name dirpath)
|
||||
if(dirpath STREQUAL "")
|
||||
return()
|
||||
endif()
|
||||
# Skip this dir if we have already registered it
|
||||
get_target_property(registered "${name}" _CMRC_REGISTERED_DIRS)
|
||||
if(dirpath IN_LIST registered)
|
||||
return()
|
||||
endif()
|
||||
# Register the parent directory first
|
||||
get_filename_component(parent "${dirpath}" DIRECTORY)
|
||||
if(NOT parent STREQUAL "")
|
||||
_cmrc_register_dirs("${name}" "${parent}")
|
||||
endif()
|
||||
# Now generate the registration
|
||||
set_property(TARGET "${name}" APPEND PROPERTY _CMRC_REGISTERED_DIRS "${dirpath}")
|
||||
_cm_encode_fpath(sym "${dirpath}")
|
||||
if(parent STREQUAL "")
|
||||
set(parent_sym root_directory)
|
||||
else()
|
||||
_cm_encode_fpath(parent_sym "${parent}")
|
||||
endif()
|
||||
get_filename_component(leaf "${dirpath}" NAME)
|
||||
set_property(
|
||||
TARGET "${name}"
|
||||
APPEND PROPERTY CMRC_MAKE_DIRS
|
||||
"static auto ${sym}_dir = ${parent_sym}_dir.directory.add_subdir(\"${leaf}\")\;"
|
||||
"root_index.emplace(\"${dirpath}\", &${sym}_dir.index_entry)\;"
|
||||
)
|
||||
endfunction()
|
||||
|
||||
function(cmrc_add_resources name)
|
||||
get_target_property(is_reslib ${name} CMRC_IS_RESOURCE_LIBRARY)
|
||||
if(NOT TARGET ${name} OR NOT is_reslib)
|
||||
message(SEND_ERROR "cmrc_add_resources called on target '${name}' which is not an existing resource library")
|
||||
return()
|
||||
endif()
|
||||
|
||||
set(options)
|
||||
set(args WHENCE PREFIX)
|
||||
set(list_args)
|
||||
cmake_parse_arguments(ARG "${options}" "${args}" "${list_args}" "${ARGN}")
|
||||
|
||||
if(NOT ARG_WHENCE)
|
||||
set(ARG_WHENCE ${CMAKE_CURRENT_SOURCE_DIR})
|
||||
endif()
|
||||
_cmrc_normalize_path(ARG_WHENCE)
|
||||
get_filename_component(ARG_WHENCE "${ARG_WHENCE}" ABSOLUTE)
|
||||
|
||||
# Generate the identifier for the resource library's namespace
|
||||
get_target_property(lib_ns "${name}" CMRC_NAMESPACE)
|
||||
|
||||
get_target_property(libdir ${name} CMRC_LIBDIR)
|
||||
get_target_property(target_dir ${name} SOURCE_DIR)
|
||||
file(RELATIVE_PATH reldir "${target_dir}" "${CMAKE_CURRENT_SOURCE_DIR}")
|
||||
if(reldir MATCHES "^\\.\\.")
|
||||
message(SEND_ERROR "Cannot call cmrc_add_resources in a parent directory from the resource library target")
|
||||
return()
|
||||
endif()
|
||||
|
||||
foreach(input IN LISTS ARG_UNPARSED_ARGUMENTS)
|
||||
_cmrc_normalize_path(input)
|
||||
get_filename_component(abs_in "${input}" ABSOLUTE)
|
||||
# Generate a filename based on the input filename that we can put in
|
||||
# the intermediate directory.
|
||||
file(RELATIVE_PATH relpath "${ARG_WHENCE}" "${abs_in}")
|
||||
if(relpath MATCHES "^\\.\\.")
|
||||
# For now we just error on files that exist outside of the soure dir.
|
||||
message(SEND_ERROR "Cannot add file '${input}': File must be in a subdirectory of ${ARG_WHENCE}")
|
||||
continue()
|
||||
endif()
|
||||
if(DEFINED ARG_PREFIX)
|
||||
_cmrc_normalize_path(ARG_PREFIX)
|
||||
endif()
|
||||
if(ARG_PREFIX AND NOT ARG_PREFIX MATCHES "/$")
|
||||
set(ARG_PREFIX "${ARG_PREFIX}/")
|
||||
endif()
|
||||
get_filename_component(dirpath "${ARG_PREFIX}${relpath}" DIRECTORY)
|
||||
_cmrc_register_dirs("${name}" "${dirpath}")
|
||||
get_filename_component(abs_out "${libdir}/intermediate/${relpath}.cpp" ABSOLUTE)
|
||||
# Generate a symbol name relpath the file's character array
|
||||
_cm_encode_fpath(sym "${relpath}")
|
||||
# Get the symbol name for the parent directory
|
||||
if(dirpath STREQUAL "")
|
||||
set(parent_sym root_directory)
|
||||
else()
|
||||
_cm_encode_fpath(parent_sym "${dirpath}")
|
||||
endif()
|
||||
# Generate the rule for the intermediate source file
|
||||
_cmrc_generate_intermediate_cpp(${lib_ns} ${sym} "${abs_out}" "${abs_in}")
|
||||
target_sources(${name} PRIVATE "${abs_out}")
|
||||
set_property(TARGET ${name} APPEND PROPERTY CMRC_EXTERN_DECLS
|
||||
"// Pointers to ${input}"
|
||||
"extern const char* const ${sym}_begin\;"
|
||||
"extern const char* const ${sym}_end\;"
|
||||
)
|
||||
get_filename_component(leaf "${relpath}" NAME)
|
||||
set_property(
|
||||
TARGET ${name}
|
||||
APPEND PROPERTY CMRC_MAKE_FILES
|
||||
"root_index.emplace("
|
||||
" \"${ARG_PREFIX}${relpath}\","
|
||||
" ${parent_sym}_dir.directory.add_file("
|
||||
" \"${leaf}\","
|
||||
" res_chars::${sym}_begin,"
|
||||
" res_chars::${sym}_end"
|
||||
" )"
|
||||
")\;"
|
||||
)
|
||||
endforeach()
|
||||
endfunction()
|
||||
|
||||
function(_cmrc_generate_intermediate_cpp lib_ns symbol outfile infile)
|
||||
add_custom_command(
|
||||
# This is the file we will generate
|
||||
OUTPUT "${outfile}"
|
||||
# These are the primary files that affect the output
|
||||
DEPENDS "${infile}" "${_CMRC_SCRIPT}"
|
||||
COMMAND
|
||||
"${CMAKE_COMMAND}"
|
||||
-D_CMRC_GENERATE_MODE=TRUE
|
||||
-DNAMESPACE=${lib_ns}
|
||||
-DSYMBOL=${symbol}
|
||||
"-DINPUT_FILE=${infile}"
|
||||
"-DOUTPUT_FILE=${outfile}"
|
||||
-P "${_CMRC_SCRIPT}"
|
||||
COMMENT "Generating intermediate file for ${infile}"
|
||||
)
|
||||
endfunction()
|
||||
|
||||
function(_cm_encode_fpath var fpath)
|
||||
string(MAKE_C_IDENTIFIER "${fpath}" ident)
|
||||
string(MD5 hash "${fpath}")
|
||||
string(SUBSTRING "${hash}" 0 4 hash)
|
||||
set(${var} f_${hash}_${ident} PARENT_SCOPE)
|
||||
endfunction()
|
||||
21
cmake/modules/CMakeRC_LICENSE.txt
Normal file
21
cmake/modules/CMakeRC_LICENSE.txt
Normal file
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2017 vector-of-bool <vectorofbool@gmail.com>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
22
cmake/modules/COPYING-CMAKE-SCRIPTS
Normal file
22
cmake/modules/COPYING-CMAKE-SCRIPTS
Normal file
@@ -0,0 +1,22 @@
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions
|
||||
are met:
|
||||
|
||||
1. Redistributions of source code must retain the copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
2. Redistributions in binary form must reproduce the copyright
|
||||
notice, this list of conditions and the following disclaimer in the
|
||||
documentation and/or other materials provided with the distribution.
|
||||
3. The name of the author may not be used to endorse or promote products
|
||||
derived from this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
|
||||
IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
|
||||
OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
|
||||
IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||||
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
|
||||
NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
||||
THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
64
cmake/modules/FindGLIB2.cmake
Normal file
64
cmake/modules/FindGLIB2.cmake
Normal file
@@ -0,0 +1,64 @@
|
||||
# - Try to find the GLIB2 libraries
|
||||
# Once done this will define
|
||||
#
|
||||
# GLIB2_FOUND - system has glib2
|
||||
# GLIB2_INCLUDE_DIR - the glib2 include directory
|
||||
# GLIB2_LIBRARIES - glib2 library
|
||||
|
||||
# Copyright (c) 2008 Laurent Montel, <montel@kde.org>
|
||||
#
|
||||
# Redistribution and use is allowed according to the terms of the BSD license.
|
||||
# For details see the accompanying COPYING-CMAKE-SCRIPTS file.
|
||||
|
||||
|
||||
if(GLIB2_INCLUDE_DIR AND GLIB2_LIBRARIES)
|
||||
# Already in cache, be silent
|
||||
set(GLIB2_FIND_QUIETLY TRUE)
|
||||
endif(GLIB2_INCLUDE_DIR AND GLIB2_LIBRARIES)
|
||||
|
||||
if (NOT WIN32)
|
||||
find_package(PkgConfig REQUIRED)
|
||||
pkg_check_modules(PKG_GLIB REQUIRED glib-2.0)
|
||||
endif(NOT WIN32)
|
||||
|
||||
find_path(GLIB2_MAIN_INCLUDE_DIR glib.h
|
||||
PATH_SUFFIXES glib-2.0
|
||||
HINTS ${PKG_GLIB_INCLUDE_DIRS} ${PKG_GLIB_INCLUDEDIR})
|
||||
|
||||
# search the glibconfig.h include dir under the same root where the library is found
|
||||
find_library(GLIB2_LIBRARIES
|
||||
NAMES glib-2.0
|
||||
HINTS ${PKG_GLIB_LIBRARY_DIRS} ${PKG_GLIB_LIBDIR})
|
||||
|
||||
find_path(GLIB2_INTERNAL_INCLUDE_DIR glibconfig.h
|
||||
PATH_SUFFIXES glib-2.0/include ../lib/glib-2.0/include
|
||||
HINTS ${PKG_GLIB_INCLUDE_DIRS} ${PKG_GLIB_LIBRARIES} ${CMAKE_SYSTEM_LIBRARY_PATH})
|
||||
|
||||
set(GLIB2_INCLUDE_DIR ${GLIB2_MAIN_INCLUDE_DIR})
|
||||
|
||||
# not sure if this include dir is optional or required
|
||||
# for now it is optional
|
||||
if(GLIB2_INTERNAL_INCLUDE_DIR)
|
||||
set(GLIB2_INCLUDE_DIR ${GLIB2_INCLUDE_DIR} ${GLIB2_INTERNAL_INCLUDE_DIR})
|
||||
endif(GLIB2_INTERNAL_INCLUDE_DIR)
|
||||
|
||||
include(FindPackageHandleStandardArgs)
|
||||
find_package_handle_standard_args(GLIB2 DEFAULT_MSG GLIB2_LIBRARIES GLIB2_MAIN_INCLUDE_DIR)
|
||||
|
||||
mark_as_advanced(GLIB2_INCLUDE_DIR GLIB2_LIBRARIES)
|
||||
|
||||
|
||||
find_program(GLIB2_GENMARSHAL_UTIL glib-genmarshal)
|
||||
|
||||
macro(glib2_genmarshal output_name)
|
||||
file(REMOVE ${CMAKE_CURRENT_BINARY_DIR}/genmarshal_tmp)
|
||||
foreach(_declaration ${ARGN})
|
||||
file(APPEND ${CMAKE_CURRENT_BINARY_DIR}/genmarshal_tmp "${_declaration}\n")
|
||||
endforeach()
|
||||
add_custom_command(
|
||||
OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/${output_name}.h ${CMAKE_CURRENT_BINARY_DIR}/${output_name}.c
|
||||
COMMAND ${GLIB2_GENMARSHAL_UTIL} --header genmarshal_tmp > ${output_name}.h
|
||||
COMMAND ${GLIB2_GENMARSHAL_UTIL} --body genmarshal_tmp > ${output_name}.c
|
||||
WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
|
||||
)
|
||||
endmacro()
|
||||
63
cmake/modules/FindGObject.cmake
Normal file
63
cmake/modules/FindGObject.cmake
Normal file
@@ -0,0 +1,63 @@
|
||||
# - Try to find GObject
|
||||
# Once done this will define
|
||||
#
|
||||
# GOBJECT_FOUND - system has GObject
|
||||
# GOBJECT_INCLUDE_DIR - the GObject include directory
|
||||
# GOBJECT_LIBRARIES - the libraries needed to use GObject
|
||||
# GOBJECT_DEFINITIONS - Compiler switches required for using GObject
|
||||
|
||||
# Copyright (c) 2006, Tim Beaulen <tbscope@gmail.com>
|
||||
# Copyright (c) 2008 Helio Chissini de Castro, <helio@kde.org>
|
||||
#
|
||||
# Redistribution and use is allowed according to the terms of the BSD license.
|
||||
# For details see the accompanying COPYING-CMAKE-SCRIPTS file.
|
||||
|
||||
|
||||
IF (GOBJECT_INCLUDE_DIR AND GOBJECT_LIBRARIES)
|
||||
# in cache already
|
||||
SET(GObject_FIND_QUIETLY TRUE)
|
||||
ELSE (GOBJECT_INCLUDE_DIR AND GOBJECT_LIBRARIES)
|
||||
SET(GObject_FIND_QUIETLY FALSE)
|
||||
ENDIF (GOBJECT_INCLUDE_DIR AND GOBJECT_LIBRARIES)
|
||||
|
||||
IF (NOT WIN32)
|
||||
FIND_PACKAGE(PkgConfig REQUIRED)
|
||||
# use pkg-config to get the directories and then use these values
|
||||
# in the FIND_PATH() and FIND_LIBRARY() calls
|
||||
PKG_CHECK_MODULES(PKG_GOBJECT2 REQUIRED gobject-2.0)
|
||||
SET(GOBJECT_DEFINITIONS ${PKG_GOBJECT2_CFLAGS})
|
||||
ENDIF (NOT WIN32)
|
||||
|
||||
FIND_PATH(GOBJECT_INCLUDE_DIR gobject/gobject.h
|
||||
HINTS ${PKG_GOBJECT2_INCLUDE_DIRS} ${PKG_GOBJECT2_INCLUDEDIR}
|
||||
PATHS /usr/include/glib-2.0/
|
||||
PATH_SUFFIXES glib-2.0
|
||||
)
|
||||
|
||||
FIND_LIBRARY(_GObjectLibs NAMES gobject-2.0
|
||||
HINTS
|
||||
${PKG_GOBJECT2_LIBRARY_DIRS}
|
||||
${PKG_GOBJECT2_LIBDIR}
|
||||
)
|
||||
FIND_LIBRARY(_GModuleLibs NAMES gmodule-2.0
|
||||
HINTS
|
||||
${PKG_GOBJECT2_LIBRARY_DIRS}
|
||||
${PKG_GOBJECT2_LIBDIR}
|
||||
)
|
||||
FIND_LIBRARY(_GThreadLibs NAMES gthread-2.0
|
||||
HINTS
|
||||
${PKG_GOBJECT2_LIBRARY_DIRS}
|
||||
${PKG_GOBJECT2_LIBDIR}
|
||||
)
|
||||
FIND_LIBRARY(_GLibs NAMES glib-2.0
|
||||
HINTS
|
||||
${PKG_GOBJECT2_LIBRARY_DIRS}
|
||||
${PKG_GOBJECT2_LIBDIR}
|
||||
)
|
||||
|
||||
SET (GOBJECT_LIBRARIES ${_GObjectLibs} ${_GModuleLibs} ${_GThreadLibs} ${_GLibs})
|
||||
|
||||
MARK_AS_ADVANCED(GOBJECT_INCLUDE_DIR GOBJECT_LIBRARIES)
|
||||
|
||||
INCLUDE(FindPackageHandleStandardArgs)
|
||||
FIND_PACKAGE_HANDLE_STANDARD_ARGS(GObject DEFAULT_MSG GOBJECT_INCLUDE_DIR GOBJECT_LIBRARIES)
|
||||
132
cmake/modules/FindGStreamer.cmake
Normal file
132
cmake/modules/FindGStreamer.cmake
Normal file
@@ -0,0 +1,132 @@
|
||||
# - Try to find GStreamer
|
||||
# Once done this will define
|
||||
#
|
||||
# GSTREAMER_FOUND - system has GStreamer
|
||||
# GSTREAMER_INCLUDE_DIR - the GStreamer include directory
|
||||
# GSTREAMER_LIBRARY - the main GStreamer library
|
||||
# GSTREAMER_PLUGIN_DIR - the GStreamer plugin directory
|
||||
#
|
||||
# And for all the plugin libraries specified in the COMPONENTS
|
||||
# of find_package, this module will define:
|
||||
#
|
||||
# GSTREAMER_<plugin_lib>_LIBRARY_FOUND - system has <plugin_lib>
|
||||
# GSTREAMER_<plugin_lib>_LIBRARY - the <plugin_lib> library
|
||||
# GSTREAMER_<plugin_lib>_INCLUDE_DIR - the <plugin_lib> include directory
|
||||
#
|
||||
# Copyright (c) 2010, Collabora Ltd.
|
||||
# @author George Kiagiadakis <george.kiagiadakis@collabora.co.uk>
|
||||
#
|
||||
# Redistribution and use is allowed according to the terms of the BSD license.
|
||||
# For details see the accompanying COPYING-CMAKE-SCRIPTS file.
|
||||
|
||||
if (GSTREAMER_INCLUDE_DIR AND GSTREAMER_LIBRARY)
|
||||
set(GStreamer_FIND_QUIETLY TRUE)
|
||||
else()
|
||||
set(GStreamer_FIND_QUIETLY FALSE)
|
||||
endif()
|
||||
|
||||
set(GSTREAMER_ABI_VERSION "1.0")
|
||||
|
||||
|
||||
# Find the main library
|
||||
find_package(PkgConfig)
|
||||
|
||||
if (PKG_CONFIG_FOUND)
|
||||
pkg_check_modules(PKG_GSTREAMER gstreamer-${GSTREAMER_ABI_VERSION})
|
||||
exec_program(${PKG_CONFIG_EXECUTABLE}
|
||||
ARGS --variable pluginsdir gstreamer-${GSTREAMER_ABI_VERSION}
|
||||
OUTPUT_VARIABLE PKG_GSTREAMER_PLUGIN_DIR)
|
||||
endif()
|
||||
|
||||
find_library(GSTREAMER_LIBRARY
|
||||
NAMES gstreamer-${GSTREAMER_ABI_VERSION}
|
||||
HINTS ${PKG_GSTREAMER_LIBRARY_DIRS} ${PKG_GSTREAMER_LIBDIR})
|
||||
|
||||
find_path(GSTREAMER_INCLUDE_DIR
|
||||
gst/gst.h
|
||||
HINTS ${PKG_GSTREAMER_INCLUDE_DIRS} ${PKG_GSTREAMER_INCLUDEDIR}
|
||||
PATH_SUFFIXES gstreamer-${GSTREAMER_ABI_VERSION})
|
||||
|
||||
if (PKG_GSTREAMER_PLUGIN_DIR)
|
||||
set(_GSTREAMER_PLUGIN_DIR ${PKG_GSTREAMER_PLUGIN_DIR})
|
||||
else()
|
||||
get_filename_component(_GSTREAMER_LIB_DIR ${GSTREAMER_LIBRARY} PATH)
|
||||
set(_GSTREAMER_PLUGIN_DIR ${_GSTREAMER_LIB_DIR}/gstreamer-${GSTREAMER_ABI_VERSION})
|
||||
endif()
|
||||
|
||||
set(GSTREAMER_PLUGIN_DIR ${_GSTREAMER_PLUGIN_DIR}
|
||||
CACHE PATH "The path to the gstreamer plugins installation directory")
|
||||
|
||||
mark_as_advanced(GSTREAMER_LIBRARY GSTREAMER_INCLUDE_DIR GSTREAMER_PLUGIN_DIR)
|
||||
|
||||
|
||||
# Find additional libraries
|
||||
include(MacroFindGStreamerLibrary)
|
||||
|
||||
macro(_find_gst_component _name _header)
|
||||
find_gstreamer_library(${_name} ${_header} ${GSTREAMER_ABI_VERSION})
|
||||
set(_GSTREAMER_EXTRA_VARIABLES ${_GSTREAMER_EXTRA_VARIABLES}
|
||||
GSTREAMER_${_name}_LIBRARY GSTREAMER_${_name}_INCLUDE_DIR)
|
||||
endmacro()
|
||||
|
||||
foreach(_component ${GStreamer_FIND_COMPONENTS})
|
||||
if (${_component} STREQUAL "base")
|
||||
_find_gst_component(BASE gstbasesink.h)
|
||||
elseif (${_component} STREQUAL "check")
|
||||
_find_gst_component(CHECK gstcheck.h)
|
||||
elseif (${_component} STREQUAL "controller")
|
||||
_find_gst_component(CONTROLLER gstargbcontrolbinding.h)
|
||||
elseif (${_component} STREQUAL "net")
|
||||
_find_gst_component(NET gstnet.h)
|
||||
else()
|
||||
message (AUTHOR_WARNING "FindGStreamerPluginsBase.cmake: Invalid component \"${_component}\" was specified")
|
||||
endif()
|
||||
endforeach()
|
||||
|
||||
|
||||
# Version check
|
||||
if (GStreamer_FIND_VERSION)
|
||||
if (PKG_GSTREAMER_FOUND)
|
||||
if("${PKG_GSTREAMER_VERSION}" VERSION_LESS "${GStreamer_FIND_VERSION}")
|
||||
message(STATUS "Found GStreamer version ${PKG_GSTREAMER_VERSION}, but at least version ${GStreamer_FIND_VERSION} is required")
|
||||
set(GSTREAMER_VERSION_COMPATIBLE FALSE)
|
||||
else()
|
||||
set(GSTREAMER_VERSION_COMPATIBLE TRUE)
|
||||
endif()
|
||||
elseif(GSTREAMER_INCLUDE_DIR)
|
||||
include(CheckCXXSourceCompiles)
|
||||
|
||||
set(CMAKE_REQUIRED_INCLUDES ${GSTREAMER_INCLUDE_DIR})
|
||||
string(REPLACE "." "," _comma_version ${GStreamer_FIND_VERSION})
|
||||
# Hack to invalidate the cached value
|
||||
set(GSTREAMER_VERSION_COMPATIBLE GSTREAMER_VERSION_COMPATIBLE)
|
||||
|
||||
check_cxx_source_compiles("
|
||||
#define G_BEGIN_DECLS
|
||||
#define G_END_DECLS
|
||||
#include <gst/gstversion.h>
|
||||
|
||||
#if GST_CHECK_VERSION(${_comma_version})
|
||||
int main() { return 0; }
|
||||
#else
|
||||
# error \"GStreamer version incompatible\"
|
||||
#endif
|
||||
" GSTREAMER_VERSION_COMPATIBLE)
|
||||
|
||||
if (NOT GSTREAMER_VERSION_COMPATIBLE)
|
||||
message(STATUS "GStreamer ${GStreamer_FIND_VERSION} is required, but the version found is older")
|
||||
endif()
|
||||
else()
|
||||
# We didn't find gstreamer at all
|
||||
set(GSTREAMER_VERSION_COMPATIBLE FALSE)
|
||||
endif()
|
||||
else()
|
||||
# No version constrain was specified, thus we consider the version compatible
|
||||
set(GSTREAMER_VERSION_COMPATIBLE TRUE)
|
||||
endif()
|
||||
|
||||
|
||||
include(FindPackageHandleStandardArgs)
|
||||
find_package_handle_standard_args(GStreamer DEFAULT_MSG
|
||||
GSTREAMER_LIBRARY GSTREAMER_INCLUDE_DIR
|
||||
GSTREAMER_VERSION_COMPATIBLE ${_GSTREAMER_EXTRA_VARIABLES})
|
||||
90
cmake/modules/FindGStreamerPluginsBase.cmake
Normal file
90
cmake/modules/FindGStreamerPluginsBase.cmake
Normal file
@@ -0,0 +1,90 @@
|
||||
# - Try to find gst-plugins-base
|
||||
# Once done this will define
|
||||
#
|
||||
# GSTREAMER_PLUGINS_BASE_FOUND - system has gst-plugins-base
|
||||
#
|
||||
# And for all the plugin libraries specified in the COMPONENTS
|
||||
# of find_package, this module will define:
|
||||
#
|
||||
# GSTREAMER_<plugin_lib>_LIBRARY_FOUND - system has <plugin_lib>
|
||||
# GSTREAMER_<plugin_lib>_LIBRARY - the <plugin_lib> library
|
||||
# GSTREAMER_<plugin_lib>_INCLUDE_DIR - the <plugin_lib> include directory
|
||||
#
|
||||
# Copyright (c) 2010, Collabora Ltd.
|
||||
# @author George Kiagiadakis <george.kiagiadakis@collabora.co.uk>
|
||||
#
|
||||
# Redistribution and use is allowed according to the terms of the BSD license.
|
||||
# For details see the accompanying COPYING-CMAKE-SCRIPTS file.
|
||||
|
||||
set(GSTREAMER_ABI_VERSION "1.0")
|
||||
|
||||
|
||||
# Find the pkg-config file for doing the version check
|
||||
find_package(PkgConfig)
|
||||
|
||||
if (PKG_CONFIG_FOUND)
|
||||
pkg_check_modules(PKG_GSTREAMER_PLUGINS_BASE gstreamer-plugins-base-${GSTREAMER_ABI_VERSION})
|
||||
endif()
|
||||
|
||||
|
||||
# Find the plugin libraries
|
||||
include(MacroFindGStreamerLibrary)
|
||||
|
||||
macro(_find_gst_plugins_base_component _name _header)
|
||||
find_gstreamer_library(${_name} ${_header} ${GSTREAMER_ABI_VERSION})
|
||||
set(_GSTREAMER_PLUGINS_BASE_EXTRA_VARIABLES ${_GSTREAMER_PLUGINS_BASE_EXTRA_VARIABLES}
|
||||
GSTREAMER_${_name}_LIBRARY GSTREAMER_${_name}_INCLUDE_DIR)
|
||||
endmacro()
|
||||
|
||||
foreach(_component ${GStreamerPluginsBase_FIND_COMPONENTS})
|
||||
if (${_component} STREQUAL "app")
|
||||
_find_gst_plugins_base_component(APP gstappsrc.h)
|
||||
elseif (${_component} STREQUAL "audio")
|
||||
_find_gst_plugins_base_component(AUDIO audio.h)
|
||||
elseif (${_component} STREQUAL "fft")
|
||||
_find_gst_plugins_base_component(FFT gstfft.h)
|
||||
elseif (${_component} STREQUAL "riff")
|
||||
_find_gst_plugins_base_component(RIFF riff-ids.h)
|
||||
elseif (${_component} STREQUAL "rtp")
|
||||
_find_gst_plugins_base_component(RTP gstrtpbuffer.h)
|
||||
elseif (${_component} STREQUAL "rtsp")
|
||||
_find_gst_plugins_base_component(RTSP gstrtspdefs.h)
|
||||
elseif (${_component} STREQUAL "sdp")
|
||||
_find_gst_plugins_base_component(SDP gstsdp.h)
|
||||
elseif (${_component} STREQUAL "tag")
|
||||
_find_gst_plugins_base_component(TAG tag.h)
|
||||
elseif (${_component} STREQUAL "pbutils")
|
||||
_find_gst_plugins_base_component(PBUTILS pbutils.h)
|
||||
elseif (${_component} STREQUAL "video")
|
||||
_find_gst_plugins_base_component(VIDEO video.h)
|
||||
elseif (${_component} STREQUAL "gl")
|
||||
_find_gst_plugins_base_component(GL gstglconfig.h)
|
||||
else()
|
||||
message (AUTHOR_WARNING "FindGStreamer.cmake: Invalid component \"${_component}\" was specified")
|
||||
endif()
|
||||
endforeach()
|
||||
|
||||
|
||||
# Version check
|
||||
if (GStreamerPluginsBase_FIND_VERSION)
|
||||
if (PKG_GSTREAMER_PLUGINS_BASE_FOUND)
|
||||
if("${PKG_GSTREAMER_PLUGINS_BASE_VERSION}" VERSION_LESS "${GStreamerPluginsBase_FIND_VERSION}")
|
||||
message(STATUS "Found gst-plugins-base version ${PKG_GSTREAMER_PLUGINS_BASE_VERSION}, but at least version ${GStreamerPluginsBase_FIND_VERSION} is required")
|
||||
set(GSTREAMER_PLUGINS_BASE_VERSION_COMPATIBLE FALSE)
|
||||
else()
|
||||
set(GSTREAMER_PLUGINS_BASE_VERSION_COMPATIBLE TRUE)
|
||||
endif()
|
||||
else()
|
||||
# We can't make any version checks without pkg-config, just assume version is compatible and hope...
|
||||
set(GSTREAMER_PLUGINS_BASE_VERSION_COMPATIBLE TRUE)
|
||||
endif()
|
||||
else()
|
||||
# No version constrain was specified, thus we consider the version compatible
|
||||
set(GSTREAMER_PLUGINS_BASE_VERSION_COMPATIBLE TRUE)
|
||||
endif()
|
||||
|
||||
|
||||
include(FindPackageHandleStandardArgs)
|
||||
find_package_handle_standard_args(GStreamerPluginsBase DEFAULT_MSG
|
||||
GSTREAMER_PLUGINS_BASE_VERSION_COMPATIBLE
|
||||
${_GSTREAMER_PLUGINS_BASE_EXTRA_VARIABLES})
|
||||
57
cmake/modules/MacroFindGStreamerLibrary.cmake
Normal file
57
cmake/modules/MacroFindGStreamerLibrary.cmake
Normal file
@@ -0,0 +1,57 @@
|
||||
# - macro find_gstreamer_library
|
||||
#
|
||||
# Copyright (c) 2010, Collabora Ltd.
|
||||
# @author George Kiagiadakis <george.kiagiadakis@collabora.co.uk>
|
||||
#
|
||||
# Redistribution and use is allowed according to the terms of the BSD license.
|
||||
# For details see the accompanying COPYING-CMAKE-SCRIPTS file.
|
||||
|
||||
macro(find_gstreamer_library _name _header _abi_version)
|
||||
string(TOLOWER ${_name} _lower_name)
|
||||
string(TOUPPER ${_name} _upper_name)
|
||||
|
||||
if (GSTREAMER_${_upper_name}_LIBRARY AND GSTREAMER_${_upper_name}_INCLUDE_DIR)
|
||||
set(_GSTREAMER_${_upper_name}_QUIET TRUE)
|
||||
else()
|
||||
set(_GSTREAMER_${_upper_name}_QUIET FALSE)
|
||||
endif()
|
||||
|
||||
if (PKG_CONFIG_FOUND)
|
||||
pkg_check_modules(PKG_GSTREAMER_${_upper_name} gstreamer-${_lower_name}-${_abi_version})
|
||||
endif()
|
||||
|
||||
find_library(GSTREAMER_${_upper_name}_LIBRARY
|
||||
NAMES gst${_lower_name}-${_abi_version}
|
||||
HINTS ${PKG_GSTREAMER_${_upper_name}_LIBRARY_DIRS}
|
||||
${PKG_GSTREAMER_${_upper_name}_LIBDIR}
|
||||
)
|
||||
|
||||
find_path(GSTREAMER_${_upper_name}_INCLUDE_DIR
|
||||
gst/${_lower_name}/${_header}
|
||||
HINTS ${PKG_GSTREAMER_${_upper_name}_INCLUDE_DIRS}
|
||||
${PKG_GSTREAMER_${_upper_name}_INCLUDEDIR}
|
||||
PATH_SUFFIXES gstreamer-${_abi_version}
|
||||
)
|
||||
|
||||
if (GSTREAMER_${_upper_name}_LIBRARY AND GSTREAMER_${_upper_name}_INCLUDE_DIR)
|
||||
set(GSTREAMER_${_upper_name}_LIBRARY_FOUND TRUE)
|
||||
else()
|
||||
set(GSTREAMER_${_upper_name}_LIBRARY_FOUND FALSE)
|
||||
endif()
|
||||
|
||||
if (NOT _GSTREAMER_${_upper_name}_QUIET)
|
||||
if (GSTREAMER_${_upper_name}_LIBRARY)
|
||||
message(STATUS "Found GSTREAMER_${_upper_name}_LIBRARY: ${GSTREAMER_${_upper_name}_LIBRARY}")
|
||||
else()
|
||||
message(STATUS "Could NOT find GSTREAMER_${_upper_name}_LIBRARY")
|
||||
endif()
|
||||
|
||||
if (GSTREAMER_${_upper_name}_INCLUDE_DIR)
|
||||
message(STATUS "Found GSTREAMER_${_upper_name}_INCLUDE_DIR: ${GSTREAMER_${_upper_name}_INCLUDE_DIR}")
|
||||
else()
|
||||
message(STATUS "Could NOT find GSTREAMER_${_upper_name}_INCLUDE_DIR")
|
||||
endif()
|
||||
endif()
|
||||
|
||||
mark_as_advanced(GSTREAMER_${_upper_name}_LIBRARY GSTREAMER_${_upper_name}_INCLUDE_DIR)
|
||||
endmacro()
|
||||
146
cmake/modules/MacroLogFeature.cmake
Normal file
146
cmake/modules/MacroLogFeature.cmake
Normal file
@@ -0,0 +1,146 @@
|
||||
# This file defines the Feature Logging macros.
|
||||
#
|
||||
# MACRO_LOG_FEATURE(VAR FEATURE DESCRIPTION URL [REQUIRED [MIN_VERSION [COMMENTS]]])
|
||||
# Logs the information so that it can be displayed at the end
|
||||
# of the configure run
|
||||
# VAR : TRUE or FALSE, indicating whether the feature is supported
|
||||
# FEATURE: name of the feature, e.g. "libjpeg"
|
||||
# DESCRIPTION: description what this feature provides
|
||||
# URL: home page
|
||||
# REQUIRED: TRUE or FALSE, indicating whether the featue is required
|
||||
# MIN_VERSION: minimum version number. empty string if unneeded
|
||||
# COMMENTS: More info you may want to provide. empty string if unnecessary
|
||||
#
|
||||
# MACRO_DISPLAY_FEATURE_LOG()
|
||||
# Call this to display the collected results.
|
||||
# Exits CMake with a FATAL error message if a required feature is missing
|
||||
#
|
||||
# Example:
|
||||
#
|
||||
# INCLUDE(MacroLogFeature)
|
||||
#
|
||||
# FIND_PACKAGE(JPEG)
|
||||
# MACRO_LOG_FEATURE(JPEG_FOUND "libjpeg" "Support JPEG images" "http://www.ijg.org" TRUE "3.2a" "")
|
||||
# ...
|
||||
# MACRO_DISPLAY_FEATURE_LOG()
|
||||
|
||||
# Copyright (c) 2006, Alexander Neundorf, <neundorf@kde.org>
|
||||
# Copyright (c) 2006, Allen Winter, <winter@kde.org>
|
||||
# Copyright (c) 2009, Sebastian Trueg, <trueg@kde.org>
|
||||
#
|
||||
# Redistribution and use is allowed according to the terms of the BSD license.
|
||||
# For details see the accompanying COPYING-CMAKE-SCRIPTS file.
|
||||
|
||||
IF (NOT _macroLogFeatureAlreadyIncluded)
|
||||
SET(_file ${CMAKE_BINARY_DIR}/MissingRequirements.txt)
|
||||
IF (EXISTS ${_file})
|
||||
FILE(REMOVE ${_file})
|
||||
ENDIF (EXISTS ${_file})
|
||||
|
||||
SET(_file ${CMAKE_BINARY_DIR}/EnabledFeatures.txt)
|
||||
IF (EXISTS ${_file})
|
||||
FILE(REMOVE ${_file})
|
||||
ENDIF (EXISTS ${_file})
|
||||
|
||||
SET(_file ${CMAKE_BINARY_DIR}/DisabledFeatures.txt)
|
||||
IF (EXISTS ${_file})
|
||||
FILE(REMOVE ${_file})
|
||||
ENDIF (EXISTS ${_file})
|
||||
|
||||
SET(_macroLogFeatureAlreadyIncluded TRUE)
|
||||
ENDIF (NOT _macroLogFeatureAlreadyIncluded)
|
||||
|
||||
|
||||
MACRO(MACRO_LOG_FEATURE _var _package _description _url ) # _required _minvers _comments)
|
||||
|
||||
STRING(TOUPPER "${ARGV4}" _required)
|
||||
SET(_minvers "${ARGV5}")
|
||||
SET(_comments "${ARGV6}")
|
||||
|
||||
IF (${_var})
|
||||
SET(_LOGFILENAME ${CMAKE_BINARY_DIR}/EnabledFeatures.txt)
|
||||
ELSE (${_var})
|
||||
IF ("${_required}" STREQUAL "TRUE")
|
||||
SET(_LOGFILENAME ${CMAKE_BINARY_DIR}/MissingRequirements.txt)
|
||||
ELSE ("${_required}" STREQUAL "TRUE")
|
||||
SET(_LOGFILENAME ${CMAKE_BINARY_DIR}/DisabledFeatures.txt)
|
||||
ENDIF ("${_required}" STREQUAL "TRUE")
|
||||
ENDIF (${_var})
|
||||
|
||||
SET(_logtext " * ${_package}")
|
||||
|
||||
IF (NOT ${_var})
|
||||
IF (${_minvers} MATCHES ".*")
|
||||
SET(_logtext "${_logtext} (${_minvers} or higher)")
|
||||
ENDIF (${_minvers} MATCHES ".*")
|
||||
SET(_logtext "${_logtext} <${_url}>\n ")
|
||||
ELSE (NOT ${_var})
|
||||
SET(_logtext "${_logtext} - ")
|
||||
ENDIF (NOT ${_var})
|
||||
|
||||
SET(_logtext "${_logtext}${_description}")
|
||||
|
||||
IF (NOT ${_var})
|
||||
IF (${_comments} MATCHES ".*")
|
||||
SET(_logtext "${_logtext}\n ${_comments}")
|
||||
ENDIF (${_comments} MATCHES ".*")
|
||||
# SET(_logtext "${_logtext}\n") #double-space missing features?
|
||||
ENDIF (NOT ${_var})
|
||||
|
||||
FILE(APPEND "${_LOGFILENAME}" "${_logtext}\n")
|
||||
|
||||
ENDMACRO(MACRO_LOG_FEATURE)
|
||||
|
||||
|
||||
MACRO(MACRO_DISPLAY_FEATURE_LOG)
|
||||
|
||||
SET(_missingFile ${CMAKE_BINARY_DIR}/MissingRequirements.txt)
|
||||
SET(_enabledFile ${CMAKE_BINARY_DIR}/EnabledFeatures.txt)
|
||||
SET(_disabledFile ${CMAKE_BINARY_DIR}/DisabledFeatures.txt)
|
||||
|
||||
IF (EXISTS ${_missingFile} OR EXISTS ${_enabledFile} OR EXISTS ${_disabledFile})
|
||||
SET(_printSummary TRUE)
|
||||
ENDIF (EXISTS ${_missingFile} OR EXISTS ${_enabledFile} OR EXISTS ${_disabledFile})
|
||||
|
||||
IF(_printSummary)
|
||||
SET(_missingDeps 0)
|
||||
IF (EXISTS ${_enabledFile})
|
||||
FILE(READ ${_enabledFile} _enabled)
|
||||
FILE(REMOVE ${_enabledFile})
|
||||
SET(_summary "${_summary}\n-----------------------------------------------------------------------------\n-- The following external packages were located on your system.\n-- This installation will have the extra features provided by these packages.\n-----------------------------------------------------------------------------\n${_enabled}")
|
||||
ENDIF (EXISTS ${_enabledFile})
|
||||
|
||||
|
||||
IF (EXISTS ${_disabledFile})
|
||||
SET(_missingDeps 1)
|
||||
FILE(READ ${_disabledFile} _disabled)
|
||||
FILE(REMOVE ${_disabledFile})
|
||||
SET(_summary "${_summary}\n-----------------------------------------------------------------------------\n-- The following OPTIONAL packages could NOT be located on your system.\n-- Consider installing them to enable more features from this software.\n-----------------------------------------------------------------------------\n${_disabled}")
|
||||
ENDIF (EXISTS ${_disabledFile})
|
||||
|
||||
|
||||
IF (EXISTS ${_missingFile})
|
||||
SET(_missingDeps 1)
|
||||
FILE(READ ${_missingFile} _requirements)
|
||||
SET(_summary "${_summary}\n-----------------------------------------------------------------------------\n-- The following REQUIRED packages could NOT be located on your system.\n-- You must install these packages before continuing.\n-----------------------------------------------------------------------------\n${_requirements}")
|
||||
FILE(REMOVE ${_missingFile})
|
||||
SET(_haveMissingReq 1)
|
||||
ENDIF (EXISTS ${_missingFile})
|
||||
|
||||
|
||||
IF (NOT ${_missingDeps})
|
||||
SET(_summary "${_summary}\n-----------------------------------------------------------------------------\n-- Congratulations! All external packages have been found.")
|
||||
ENDIF (NOT ${_missingDeps})
|
||||
|
||||
|
||||
MESSAGE(${_summary})
|
||||
MESSAGE("-----------------------------------------------------------------------------\n")
|
||||
|
||||
|
||||
IF(_haveMissingReq)
|
||||
MESSAGE(FATAL_ERROR "Exiting: Missing Requirements")
|
||||
ENDIF(_haveMissingReq)
|
||||
|
||||
ENDIF(_printSummary)
|
||||
|
||||
ENDMACRO(MACRO_DISPLAY_FEATURE_LOG)
|
||||
25
imgui.ini
Normal file
25
imgui.ini
Normal file
@@ -0,0 +1,25 @@
|
||||
[Window][Debug##Default]
|
||||
Pos=60,60
|
||||
Size=400,400
|
||||
Collapsed=0
|
||||
|
||||
[Window][v-mix]
|
||||
Pos=40,40
|
||||
Size=300,300
|
||||
Collapsed=0
|
||||
|
||||
[Window][Media Player]
|
||||
Pos=8,678
|
||||
Size=947,624
|
||||
Collapsed=0
|
||||
|
||||
[Window][Text Editor]
|
||||
Pos=696,206
|
||||
Size=800,600
|
||||
Collapsed=0
|
||||
|
||||
[Window][Image properties]
|
||||
Pos=511,51
|
||||
Size=172,256
|
||||
Collapsed=0
|
||||
|
||||
354
main.cpp
Normal file
354
main.cpp
Normal file
@@ -0,0 +1,354 @@
|
||||
|
||||
#include <stdio.h>
|
||||
#include <iostream>
|
||||
|
||||
// standalone image loader
|
||||
#include "stb_image.h"
|
||||
|
||||
// Opengl
|
||||
#include <glad/glad.h>
|
||||
#include <glm/glm.hpp>
|
||||
|
||||
// GStreamer
|
||||
#include <gst/gst.h>
|
||||
#include <gst/gstbin.h>
|
||||
#include <gst/gl/gl.h>
|
||||
|
||||
// vmix
|
||||
#include "defines.h"
|
||||
#include "ShaderManager.h"
|
||||
#include "SettingsManager.h"
|
||||
#include "ResourceManager.h"
|
||||
#include "RenderingManager.h"
|
||||
#include "UserInterfaceManager.h"
|
||||
|
||||
#include "imgui.h"
|
||||
#include "ImGuiToolkit.h"
|
||||
#include "GstToolkit.h"
|
||||
#include "MediaPlayer.h"
|
||||
|
||||
#define PI 3.14159265358979323846
|
||||
|
||||
MediaPlayer testmedia("testmedia");
|
||||
MediaPlayer testmedia2("testmedia2");
|
||||
Shader rendering_shader;
|
||||
unsigned int vbo, vao, ebo;
|
||||
float texturear = 1.0;
|
||||
GLuint textureimagepng = 0;
|
||||
|
||||
void create_square(unsigned int &vbo, unsigned int &vao, unsigned int &ebo)
|
||||
{
|
||||
|
||||
// create the triangle
|
||||
float _vertices[] = {
|
||||
-1.f, -1.f, 0.0f, // position vertex 3
|
||||
0.0f, 1.0f, 0.0f, // uv vertex 3
|
||||
-1.f, 1.f, 0.0f, // position vertex 0
|
||||
0.0f, 0.0f, 0.0f, // uv vertex 0
|
||||
1.f, -1.f, 0.0f, // position vertex 2
|
||||
1.0f, 1.0f, 0.0f, // uv vertex 2
|
||||
1.f, 1.f, 0.0f, // position vertex 1
|
||||
1.0f, 0.0f, 0.0f, // uv vertex 1
|
||||
};
|
||||
unsigned int _indices[] = { 0, 1, 2, 3 };
|
||||
glGenVertexArrays(1, &vao);
|
||||
glGenBuffers(1, &vbo);
|
||||
glGenBuffers(1, &ebo);
|
||||
glBindVertexArray(vao);
|
||||
glBindBuffer(GL_ARRAY_BUFFER, vbo);
|
||||
glBufferData(GL_ARRAY_BUFFER, sizeof(_vertices), _vertices, GL_STATIC_DRAW);
|
||||
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ebo);
|
||||
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(_indices), _indices, GL_STATIC_DRAW);
|
||||
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(float), (void *)0);
|
||||
glEnableVertexAttribArray(0);
|
||||
glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(float), (void *)(3 * sizeof(float)));
|
||||
glEnableVertexAttribArray(1);
|
||||
glBindBuffer(GL_ARRAY_BUFFER, 0);
|
||||
glBindVertexArray(0);
|
||||
|
||||
}
|
||||
|
||||
GLuint loadPNG(const char *imagepath, float *aspectrato)
|
||||
{
|
||||
int w, h, n, image;
|
||||
/* FIXME: remove once the example supports gl3 and/or gles2 */
|
||||
g_setenv ("GST_GL_API", "opengl", FALSE);
|
||||
unsigned char* img;
|
||||
*aspectrato = 1.f;
|
||||
|
||||
img = stbi_load(imagepath, &w, &h, &n, 3);
|
||||
if (img == NULL) {
|
||||
printf("Failed to load png - %s\n", stbi_failure_reason());
|
||||
return 0;
|
||||
}
|
||||
GLuint tex;
|
||||
glGenTextures(1, &tex);
|
||||
|
||||
glBindTexture( GL_TEXTURE_2D, tex);
|
||||
glPixelStorei(GL_UNPACK_ALIGNMENT, 3);
|
||||
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
|
||||
|
||||
//glPixelStorei(GL_UNPACK_ROW_LENGTH, 0);
|
||||
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, w, h, 0, GL_RGB, GL_UNSIGNED_BYTE, img);
|
||||
|
||||
stbi_image_free(img);
|
||||
glBindTexture( GL_TEXTURE_2D, 0);
|
||||
|
||||
*aspectrato = static_cast<float>(w) / static_cast<float>(h);
|
||||
return tex;
|
||||
}
|
||||
|
||||
// from https://gstreamer.freedesktop.org/documentation/application-development/advanced/pipeline-manipulation.html?gi-language=c
|
||||
|
||||
void drawMediaBackgound()
|
||||
{
|
||||
if ( !testmedia2.isOpen() )
|
||||
return;
|
||||
|
||||
|
||||
// rendering TRIANGLE geometries
|
||||
rendering_shader.use();
|
||||
rendering_shader.setUniform("render_projection", Rendering::Projection());
|
||||
|
||||
|
||||
testmedia2.Update();
|
||||
testmedia2.Bind();
|
||||
|
||||
// rendering_shader.setUniform("aspectratio", texturear);
|
||||
// glBindTexture(GL_TEXTURE_2D, textureimagepng);
|
||||
|
||||
glBindVertexArray(vao);
|
||||
glDrawElements(GL_TRIANGLE_STRIP, 4, GL_UNSIGNED_INT, 0);
|
||||
glBindVertexArray(0);
|
||||
|
||||
glBindTexture(GL_TEXTURE_2D, 0);
|
||||
|
||||
// render TRIANGLE GUI
|
||||
{
|
||||
ImGui::Begin("Image properties");
|
||||
|
||||
static float translation[] = {0.f, 0.f};
|
||||
if (ImGuiToolkit::ButtonIcon(6, 15)) {
|
||||
translation[0] = 0.f;
|
||||
translation[1] = 0.f;
|
||||
}
|
||||
ImGui::SameLine(0, 10);
|
||||
ImGui::SliderFloat2("position", translation, -5.0, 5.0);
|
||||
|
||||
static float rotation = 0.f;
|
||||
if (ImGuiToolkit::ButtonIcon(4, 15))
|
||||
rotation = 0.f;
|
||||
ImGui::SameLine(0, 10);
|
||||
ImGui::SliderFloat("rotation", &rotation, 0, 2 * PI);
|
||||
|
||||
static float scale = 1.f;
|
||||
if (ImGuiToolkit::ButtonIcon(3, 15))
|
||||
scale = 1.f;
|
||||
ImGui::SameLine(0, 10);
|
||||
ImGui::SliderFloat("scale", &scale, 0.1f, 10.f, "%.3f", 3.f);
|
||||
|
||||
// color picker
|
||||
static float color[4] = { 1.0f,1.0f,1.0f,1.0f };
|
||||
if (ImGuiToolkit::ButtonIcon(16, 8)) {
|
||||
color[0] = 1.f;
|
||||
color[1] = 1.f;
|
||||
color[2] = 1.f;
|
||||
color[3] = 1.f;
|
||||
}
|
||||
ImGui::SameLine(0, 10);
|
||||
ImGui::ColorEdit3("color", color);
|
||||
|
||||
static float brightness = 0.0;
|
||||
if (ImGuiToolkit::ButtonIcon(4, 1))
|
||||
brightness = 0.f;
|
||||
ImGui::SameLine(0, 10);
|
||||
ImGui::SliderFloat("brightness", &brightness, -1.0, 1.0, "%.3f", 2.f);
|
||||
|
||||
static float contrast = 0.0;
|
||||
if (ImGuiToolkit::ButtonIcon(2, 1))
|
||||
contrast = 0.f;
|
||||
ImGui::SameLine(0, 10);
|
||||
ImGui::SliderFloat("contrast", &contrast, -1.0, 1.0, "%.3f", 2.f);
|
||||
|
||||
// pass the parameters to the shader
|
||||
rendering_shader.setUniform("scale", scale);
|
||||
rendering_shader.setUniform("rotation", rotation);
|
||||
rendering_shader.setUniform("translation", translation[0], translation[1]);
|
||||
rendering_shader.setUniform("color", color[0], color[1], color[2]);
|
||||
rendering_shader.setUniform("brightness", brightness);
|
||||
rendering_shader.setUniform("contrast", contrast);
|
||||
rendering_shader.setUniform("aspectratio", testmedia2.AspectRatio());
|
||||
|
||||
ImGui::End();
|
||||
}
|
||||
|
||||
// // add gui elements to vmix main window
|
||||
// ImGui::Begin("v-mix");
|
||||
// static float zoom = 0.3f;
|
||||
// if (ImGuiToolkit::ButtonIcon(5, 7))
|
||||
// zoom = 0.3f;
|
||||
// ImGui::SameLine(0, 10);
|
||||
// if (ImGui::SliderFloat("zoom", &zoom, 0.1f, 10.f, "%.4f", 3.f))
|
||||
// UserInterface::Log("Zoom %f", zoom);
|
||||
// rendering_shader.setUniform("render_zoom", zoom);
|
||||
// ImGui::End();
|
||||
|
||||
|
||||
Shader::enduse();
|
||||
|
||||
}
|
||||
|
||||
void drawMediaPlayer()
|
||||
{
|
||||
if ( !testmedia.isOpen() )
|
||||
return;
|
||||
|
||||
testmedia.Update();
|
||||
|
||||
ImGui::Begin("Media Player");
|
||||
float width = ImGui::GetContentRegionAvail().x;
|
||||
float spacing = ImGui::GetStyle().ItemInnerSpacing.x;
|
||||
|
||||
ImVec2 imagesize ( width, width / testmedia.AspectRatio());
|
||||
ImGui::Image((void*)(intptr_t)testmedia.Texture(), imagesize);
|
||||
|
||||
if (ImGuiToolkit::ButtonIcon(17, 7)) testmedia.Rewind();
|
||||
ImGui::SameLine(0, spacing);
|
||||
if (testmedia.isPlaying()) {
|
||||
// if (ImGuiToolkit::ButtonIcon(2, 8))
|
||||
if (ImGui::Button(ICON_FA_STOP " Stop") )
|
||||
testmedia.Play(false);
|
||||
ImGui::SameLine(0, spacing);
|
||||
ImGui::PushButtonRepeat(true);
|
||||
// if (ImGui::Button(ICON_FA_FAST_FORWARD))
|
||||
if (ImGuiToolkit::ButtonIcon(14, 7))
|
||||
{
|
||||
testmedia.FastForward ();
|
||||
}
|
||||
ImGui::PopButtonRepeat();
|
||||
} else {
|
||||
// if (ImGuiToolkit::ButtonIcon(9, 7))
|
||||
if (ImGui::Button(ICON_FA_PLAY " Play") )
|
||||
testmedia.Play(true);
|
||||
ImGui::SameLine(0, spacing);
|
||||
if (ImGuiToolkit::ButtonIcon(8, 0)) testmedia.SeekNextFrame();
|
||||
}
|
||||
|
||||
ImGui::SameLine(0, spacing * 2.f);
|
||||
ImGui::Dummy(ImVec2(width - 700.0, 0)); // right align
|
||||
|
||||
static int current_loop = 1;
|
||||
static const char* loop_names[3] = { "Stop", "Rewind", "Bounce" };
|
||||
const char* current_loop_name = loop_names[current_loop];
|
||||
ImGui::SameLine(0, spacing);
|
||||
if (current_loop == 0) ImGuiToolkit::Icon(0, 15);
|
||||
else if (current_loop == 1) ImGuiToolkit::Icon(1, 15);
|
||||
else ImGuiToolkit::Icon(19, 14);
|
||||
ImGui::SameLine(0, spacing);
|
||||
ImGui::SetNextItemWidth(90);
|
||||
if ( ImGui::SliderInt("", ¤t_loop, 0, 2, current_loop_name) )
|
||||
testmedia.setLoop( (MediaPlayer::LoopMode) current_loop );
|
||||
|
||||
float speed = static_cast<float>(testmedia.PlaySpeed());
|
||||
ImGui::SameLine(0, spacing);
|
||||
ImGui::SetNextItemWidth(270);
|
||||
// ImGui::SetNextItemWidth(width - 90.0);
|
||||
if (ImGui::SliderFloat( "Speed", &speed, -10.f, 10.f, "x %.1f", 2.f))
|
||||
testmedia.SetPlaySpeed( static_cast<double>(speed) );
|
||||
ImGui::SameLine(0, spacing);
|
||||
if (ImGuiToolkit::ButtonIcon(19, 15)) {
|
||||
speed = 1.f;
|
||||
testmedia.SetPlaySpeed( static_cast<double>(speed) );
|
||||
}
|
||||
|
||||
//ImGuiToolkit::Bar(0.6f, 0.1, 0.8, 0.0, 1.0, "time", true);
|
||||
|
||||
guint64 t = testmedia.Position();
|
||||
guint64 begin = testmedia.Duration() / 5;
|
||||
guint64 end = 4 * testmedia.Duration() / 5;
|
||||
if (ImGuiToolkit::TimelineSlider( "timeline", &t, begin, end, testmedia.Duration(), testmedia.FrameDuration()) )
|
||||
{
|
||||
testmedia.SeekTo(t);
|
||||
|
||||
}
|
||||
|
||||
ImGui::Text("Dimension %d x %d", testmedia.Width(), testmedia.Height());
|
||||
ImGui::Text("Framerate %.2f / %.2f", testmedia.UpdateFrameRate() , testmedia.FrameRate() );
|
||||
|
||||
ImGui::End();
|
||||
}
|
||||
|
||||
int main(int, char**)
|
||||
{
|
||||
///
|
||||
/// Settings
|
||||
///
|
||||
Settings::Load();
|
||||
|
||||
///
|
||||
/// RENDERING INIT
|
||||
///
|
||||
if ( !Rendering::Init() )
|
||||
return 1;
|
||||
|
||||
///
|
||||
/// UI INIT
|
||||
///
|
||||
if ( !UserInterface::Init() )
|
||||
return 1;
|
||||
|
||||
///
|
||||
/// GStreamer
|
||||
///
|
||||
gst_debug_set_active(TRUE);
|
||||
gst_debug_set_default_threshold (GST_LEVEL_WARNING);
|
||||
|
||||
// 1
|
||||
// testmedia.Open("file:///home/bhbn/Images/2014-10-18_16-46-47.jpg");
|
||||
// testmedia.Open("file:///home/bhbn/Videos/balls.gif");
|
||||
// testmedia.Open("file:///home/bhbn/Videos/SIGGRAPH92_1.avi");
|
||||
// testmedia.Open("file:///home/bhbn/Videos/fish.mp4");
|
||||
// testmedia.Open("file:///home/bhbn/Videos/iss.mov");
|
||||
// testmedia.Open("file:///home/bhbn/Videos/TearsOfSteel_720p_h265.mkv");
|
||||
testmedia.Open("file:///home/bhbn/Videos/Upgrade.2018.720p.AMZN.WEB-DL.DDP5.1.H.264-NTG.m4v");
|
||||
|
||||
testmedia.Play(false);
|
||||
// Add draw callbacks to the Rendering
|
||||
Rendering::AddDrawCallback(drawMediaPlayer);
|
||||
|
||||
// 2
|
||||
testmedia2.Open("file:///home/bhbn/Videos/iss.mov");
|
||||
// testmedia2.Open("file:///home/bhbn/Videos/balls.gif");
|
||||
// testmedia2.Open("file:///home/bhbn/Videos/Upgrade.2018.720p.AMZN.WEB-DL.DDP5.1.H.264-NTG.m4v");
|
||||
testmedia2.Play(true);
|
||||
// create our geometries
|
||||
create_square(vbo, vao, ebo);
|
||||
// Add draw callbacks to the Rendering
|
||||
Rendering::AddDrawCallback(drawMediaBackgound);
|
||||
|
||||
// // load an image
|
||||
textureimagepng = loadPNG("/home/bhbn/Videos/iss_snap.png", &texturear);
|
||||
|
||||
// init shader
|
||||
rendering_shader.load("shaders/texture-shader.vs", "shaders/texture-shader.fs");
|
||||
|
||||
UserInterface::OpenTextEditor( Resource::getText("shaders/texture-shader.vs") );
|
||||
|
||||
// Main loop
|
||||
while ( Rendering::isActive() )
|
||||
{
|
||||
|
||||
Rendering::Draw();
|
||||
}
|
||||
|
||||
testmedia.Close();
|
||||
testmedia2.Close();
|
||||
|
||||
UserInterface::Terminate();
|
||||
Rendering::Terminate();
|
||||
|
||||
Settings::Save();
|
||||
|
||||
return 0;
|
||||
}
|
||||
14
osx/CocoaToolkit.h
Normal file
14
osx/CocoaToolkit.h
Normal file
@@ -0,0 +1,14 @@
|
||||
#ifndef COCOA_TOOLKIT_H
|
||||
#define COCOA_TOOLKIT_H
|
||||
|
||||
#ifndef APPLE
|
||||
#error "This file is only meant to be compiled for Mac OS X targets"
|
||||
#endif
|
||||
|
||||
namespace CocoaToolkit {
|
||||
|
||||
void * get_current_nsopengl_context();
|
||||
|
||||
};
|
||||
|
||||
#endif // COCOA_TOOLKIT_H
|
||||
28
osx/CocoaToolkit.mm
Normal file
28
osx/CocoaToolkit.mm
Normal file
@@ -0,0 +1,28 @@
|
||||
/*
|
||||
* GStreamer
|
||||
* Copyright (C) 2010 Julien Isorce <julien.isorce@gmail.com>
|
||||
* Copyright (C) 2010 Nuno Santos <nunosantos@imaginando.net>
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Library General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Library General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Library General Public
|
||||
* License along with this library; if not, write to the
|
||||
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
#include "CocoaToolkit.h"
|
||||
|
||||
#import <Cocoa/Cocoa.h>
|
||||
|
||||
void * CocoaToolkit::get_current_nsopengl_context()
|
||||
{
|
||||
return CGLGetCurrentContext();
|
||||
}
|
||||
Reference in New Issue
Block a user