/* * This file is part of vimix - video live mixer * * **Copyright** (C) 2019-2022 Bruno Herbelin * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . **/ #include #include #include #include #include #include #include #include #include "ImGuiToolkit.h" #include "imgui_internal.h" #include "Log.h" #include "ImageFilter.h" #include "Mixer.h" #include "defines.h" #include "Source.h" #include "Settings.h" #include "PickingVisitor.h" #include "Decorations.h" #include "UserInterfaceManager.h" #include "BoundingBoxVisitor.h" #include "RenderingManager.h" #include "DisplaysView.h" #define WINDOW_TITLEBAR_HEIGHT 0.03f FilteringProgram _whitebalance("Whitebalance", "shaders/filters/whitebalance.glsl", "", { { "Red", 1.0}, { "Green", 1.0}, { "Blue", 1.0}, { "Temperature", 0.5} }); DisplaysView::DisplaysView() : View(DISPLAYS) { scene.root()->scale_ = glm::vec3(DISPLAYS_DEFAULT_SCALE, DISPLAYS_DEFAULT_SCALE, 1.0f); // read default settings if ( Settings::application.views[mode_].name.empty() ) { // no settings found: store application default saveSettings(); } else restoreSettings(); Settings::application.views[mode_].name = "Displays"; // create and attach all window manipulation objects windows_ = std::vector(MAX_OUTPUT_WINDOW); for (auto w = windows_.begin(); w != windows_.end(); ++w){ // root node w->root_ = new Group; scene.ws()->attach(w->root_); w->root_->visible_ = false; // surface background and texture w->surface_ = new Surface; w->root_->attach(w->surface_); w->shader_ = new ImageFilteringShader; w->shader_->setCode( _whitebalance.code().first ); w->render_ = new Surface(w->shader_); w->root_->attach(w->render_); // icon if disabled w->icon_ = new Handles(Handles::EYESLASHED); w->icon_->visible_ = false; w->icon_->color = glm::vec4( COLOR_WINDOW, 1.f ); w->root_->attach(w->icon_); // overlays for selected and not selected w->overlays_ = new Switch; w->root_->attach(w->overlays_); // overlays_ [0] is for not active frame Frame *frame = new Frame(Frame::SHARP, Frame::THIN, Frame::DROP); frame->color = glm::vec4( COLOR_WINDOW, 1.f ); w->overlays_->attach(frame); // overlays_ [1] is for active frame Group *g = new Group; w->overlays_->attach(g); // Overlay menu icon w->menu_ = new Handles(Handles::MENU); w->menu_->color = glm::vec4( COLOR_WINDOW, 1.f ); g->attach(w->menu_); // selected frame frame = new Frame(Frame::SHARP, Frame::LARGE, Frame::NONE); frame->color = glm::vec4( COLOR_WINDOW, 1.f ); g->attach(frame); // Overlay has two modes : window or fullscreen w->mode_ = new Switch; g->attach(w->mode_); // mode_ [0] is for WINDOWED w->handles_ = new Handles(Handles::SCALE); w->handles_->color = glm::vec4( COLOR_WINDOW, 1.f ); w->mode_->attach(w->handles_); // mode_ [1] is for FULLSCREEN w->fullscreen_ = new Symbol(Symbol::TELEVISION); w->fullscreen_->scale_ = glm::vec3(2.f, 2.f, 1.f); w->fullscreen_->color = glm::vec4( COLOR_WINDOW, 1.f ); w->mode_->attach(w->fullscreen_); // title bar w->title_ = new Surface (new Shader); w->title_->shader()->color = glm::vec4( COLOR_WINDOW, 1.f ); w->title_->scale_ = glm::vec3(1.002f, WINDOW_TITLEBAR_HEIGHT, 1.f); w->title_->translation_ = glm::vec3(0.f, 1.f + WINDOW_TITLEBAR_HEIGHT, 0.f); w->root_->attach(w->title_); // default to not active & window overlay frame w->overlays_->setActive(0); w->mode_->setActive(0); } // initial behavior: no window selected, no menu show_window_menu_ = false; current_window_ = -1; current_window_status_ = new Group; draw_pending_ = false; // display actions : 0 = move output, 1 paint, 2 erase display_action_ = 0; output_ar = 1.f; } void DisplaysView::update(float dt) { View::update(dt); // specific update when this view is active if ( Mixer::manager().view() == this ) { // update rendering of render frame for (int i = 0; i < MAX_OUTPUT_WINDOW; ++i) windows_[i].render_->setTextureIndex( Rendering::manager().outputWindow(i).texture() ); output_ar = Mixer::manager().session()->frame()->aspectRatio(); } } // recenter is called also when RenderingManager detects a change of monitors void DisplaysView::recenter () { // clear background display of monitors scene.clearBackground(); scene.clearForeground(); // fill scene background with the frames to show monitors int index = 1; std::map _monitors = Rendering::manager().monitors(); for (auto monitor_iter = _monitors.begin(); monitor_iter != _monitors.end(); ++monitor_iter, ++index) { // get coordinates of monitor in Display units glm::vec4 rect = DISPLAYS_UNIT * glm::vec4(monitor_iter->second); // add a background dark surface with glow shadow Group *m = new Group; m->scale_ = glm::vec3( 0.5f * rect.p, 0.5f * rect.q, 1.f ); m->translation_ = glm::vec3( rect.x + m->scale_.x, -rect.y - m->scale_.y, 0.f ); Surface *surf = new Surface( new Shader); surf->shader()->color = glm::vec4( 0.1f, 0.1f, 0.1f, 1.f ); m->attach(surf); // cyan color frame Frame *frame = new Frame(Frame::SHARP, Frame::THIN, Frame::GLOW); frame->color = glm::vec4( COLOR_MONITOR, 1.f); m->attach(frame); // central label Glyph *label = new Glyph(4); label->setChar( std::to_string(index).back() ); label->color = glm::vec4( COLOR_MONITOR, 1.f ); label->translation_.y = 0.02f ; label->scale_.y = 0.4f / rect.p; m->attach(label); scene.bg()->attach( m ); // add a foreground color frame (semi transparent for overlay) Group *f = new Group; f->copyTransform(m); frame = new Frame(Frame::SHARP, Frame::THIN, Frame::NONE); frame->color = glm::vec4( COLOR_MONITOR, 0.2f ); f->attach(frame); scene.fg()->attach(f); // g_printerr("- display %f,%f %f,%f\n", rect.x, rect.y, rect.p, rect.q); // g_printerr(" %f,%f %f,%f\n", m->translation_.x, m->translation_.y, // m->scale_.x, m->scale_.y); } // calculate screen area required to see the entire scene BoundingBoxVisitor scene_visitor_bbox(true); scene.accept(scene_visitor_bbox); GlmToolkit::AxisAlignedBoundingBox scene_box = scene_visitor_bbox.bbox(); // compute scaling to fit the scene box into the scene, modulo a margin ratio scene.root()->scale_.x = SCENE_UNIT / ( scene_box.scale().x * 1.3f); scene.root()->scale_.y = scene.root()->scale_.x; // compute translation to place at the center (considering scaling) scene.root()->translation_ = -scene.root()->scale_.x * scene_box.center(); } void DisplaysView::resize ( int scale ) { float z = CLAMP(0.01f * (float) scale, 0.f, 1.f); z *= z; // square z *= DISPLAYS_MAX_SCALE - DISPLAYS_MIN_SCALE; z += DISPLAYS_MIN_SCALE; scene.root()->scale_.x = z; scene.root()->scale_.y = z; } int DisplaysView::size () { float z = (scene.root()->scale_.x - DISPLAYS_MIN_SCALE) / (DISPLAYS_MAX_SCALE - DISPLAYS_MIN_SCALE); return (int) ( sqrt(z) * 100.f); } void DisplaysView::draw() { // draw all windows int i = 0; for (; i < Settings::application.num_output_windows; ++i) { // update visible flag windows_[i].root_->visible_ = true; windows_[i].icon_->visible_ = Settings::application.render.disabled; if (windows_[i].render_->visible_) { // rendering of framebuffer in window if (Settings::application.windows[i+1].scaled) { windows_[i].render_->scale_ = glm::vec3(1.f, 1.f, 1.f); } else { float out_ar = windows_[i].root_->scale_.x / windows_[i].root_->scale_.y; if (output_ar < out_ar) windows_[i].render_->scale_ = glm::vec3(output_ar / out_ar, 1.f, 1.f); else windows_[i].render_->scale_ = glm::vec3(1.f, out_ar / output_ar, 1.f); } if (windows_[i].shader_) { windows_[i].shader_->uniforms_["Red"] = Settings::application.windows[i+1].whitebalance.x; windows_[i].shader_->uniforms_["Green"] = Settings::application.windows[i+1].whitebalance.y; windows_[i].shader_->uniforms_["Blue"] = Settings::application.windows[i+1].whitebalance.z; windows_[i].shader_->uniforms_["Temperature"] = Settings::application.windows[i+1].whitebalance.w; } } // update overlay if ( Settings::application.windows[i+1].fullscreen ) { // output overlay for fullscreen windows_[i].mode_->setActive( 1 ); windows_[i].title_->visible_ = false; glm::ivec4 rect = Rendering::manager().monitors()[Settings::application.windows[i+1].monitor]; glm::vec2 TopLeft = glm::vec2(rect.x * DISPLAYS_UNIT, -rect.y * DISPLAYS_UNIT); TopLeft = Rendering::manager().project(glm::vec3(TopLeft, 0.f), scene.root()->transform_, false); ImGui::SetNextWindowPos( ImVec2( TopLeft.x, TopLeft.y), ImGuiCond_Always); glm::vec2 BottomRight = glm::vec2( (rect.x + rect.p) * DISPLAYS_UNIT, -(rect.y + rect.q) * DISPLAYS_UNIT); BottomRight = Rendering::manager().project(glm::vec3(BottomRight, 0.f), scene.root()->transform_, false); ImGui::SetNextWindowSize( ImVec2( BottomRight.x - TopLeft.x, BottomRight.y - TopLeft.y), ImGuiCond_Always); ImGui::SetNextWindowBgAlpha(0.0f); // Transparent background if (ImGui::Begin(Settings::application.windows[i+1].name.c_str(), NULL, ImGuiWindowFlags_NoInputs | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_NoFocusOnAppearing)) { ImDrawList* draw_list = ImGui::GetWindowDrawList(); draw_list->AddRectFilled(ImVec2(TopLeft.x, TopLeft.y + 4), ImVec2(BottomRight.x, TopLeft.y + ImGui::GetTextLineHeightWithSpacing()), IMGUI_COLOR_OVERLAY); ImGuiToolkit::PushFont(ImGuiToolkit::FONT_MONO); ImGui::TextColored(ImVec4(COLOR_WINDOW, 1.0f), ICON_FA_TV " %d %s %d x %d px", i+1, Settings::application.windows[i+1].monitor.c_str(), rect.p, rect.q); ImGui::PopFont(); ImGui::End(); } } else { // output overlay for window windows_[i].mode_->setActive( 0 ); windows_[i].title_->visible_ = Settings::application.windows[i+1].decorated; glm::vec2 TopLeft = glm::vec2(Settings::application.windows[i+1].x * DISPLAYS_UNIT, -Settings::application.windows[i+1].y * DISPLAYS_UNIT); TopLeft = Rendering::manager().project(glm::vec3(TopLeft, 0.f), scene.root()->transform_, false); ImGui::SetNextWindowPos( ImVec2( TopLeft.x, TopLeft.y), ImGuiCond_Always); glm::vec2 BottomRight = glm::vec2( (Settings::application.windows[i+1].x + Settings::application.windows[i+1].w) * DISPLAYS_UNIT, -(Settings::application.windows[i+1].y + Settings::application.windows[i+1].h) * DISPLAYS_UNIT); BottomRight = Rendering::manager().project(glm::vec3(BottomRight, 0.f), scene.root()->transform_, false); ImGui::SetNextWindowSize( ImVec2( BottomRight.x - TopLeft.x, BottomRight.y - TopLeft.y), ImGuiCond_Always); ImGui::SetNextWindowBgAlpha(0.0f); // Transparent background if (ImGui::Begin(Settings::application.windows[i+1].name.c_str(), NULL, ImGuiWindowFlags_NoInputs | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_NoFocusOnAppearing)) { ImDrawList* draw_list = ImGui::GetWindowDrawList(); draw_list->AddRectFilled(ImVec2(TopLeft.x, TopLeft.y + 4), ImVec2(BottomRight.x, TopLeft.y + ImGui::GetTextLineHeightWithSpacing()), IMGUI_COLOR_OVERLAY); ImGuiToolkit::PushFont(ImGuiToolkit::FONT_MONO); ImGui::TextColored(ImVec4(COLOR_WINDOW, 1.0f), ICON_FA_WINDOW_MAXIMIZE " %d (%d,%d) %d x %d px", i+1, Settings::application.windows[i+1].x, Settings::application.windows[i+1].y, Settings::application.windows[i+1].w, Settings::application.windows[i+1].h); ImGui::PopFont(); ImGui::End(); } } } for (; i < MAX_OUTPUT_WINDOW; ++i) windows_[i].root_->visible_ = false; // if user is not manipulating output frame // update the output frame to match the window dimensions // TODO Mutex for multithread access with changed flag // TODO less busy update? if (!current_action_ongoing_ && !draw_pending_) { for (int i = 0; i < Settings::application.num_output_windows; ++i) { if ( Settings::application.windows[i+1].fullscreen ) { glm::ivec4 rect = Rendering::manager().monitors()[Settings::application.windows[i+1].monitor]; windows_[i].root_->scale_.x = rect.p * 0.5f * DISPLAYS_UNIT; windows_[i].root_->scale_.y = rect.q * 0.5f * DISPLAYS_UNIT; windows_[i].root_->translation_.x = rect.x * DISPLAYS_UNIT + windows_[i].root_->scale_.x; windows_[i].root_->translation_.y = -rect.y * DISPLAYS_UNIT - windows_[i].root_->scale_.y; } else { windows_[i].root_->scale_.x = Settings::application.windows[i+1].w * 0.5f * DISPLAYS_UNIT; windows_[i].root_->scale_.y = Settings::application.windows[i+1].h * 0.5f * DISPLAYS_UNIT; windows_[i].root_->translation_.x = Settings::application.windows[i+1].x * DISPLAYS_UNIT + windows_[i].root_->scale_.x; windows_[i].root_->translation_.y = -Settings::application.windows[i+1].y * DISPLAYS_UNIT - windows_[i].root_->scale_.y; windows_[i].title_->scale_.y = WINDOW_TITLEBAR_HEIGHT / windows_[i].root_->scale_.y; windows_[i].title_->translation_.y = 1.f + windows_[i].title_->scale_.y; } } } // main call to draw the view View::draw(); // display interface // Locate window at upper left corner glm::vec2 P = glm::vec2(0.01f, 0.01 ); P = Rendering::manager().project(glm::vec3(P, 0.f), scene.root()->transform_, false); // Set window position depending on icons size ImGuiToolkit::PushFont(ImGuiToolkit::FONT_LARGE); ImGui::SetNextWindowPos(ImVec2(P.x, P.y - 1.5f * ImGui::GetFrameHeight() ), ImGuiCond_Always); if (ImGui::Begin("##DisplaysMaskOptions", NULL, ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoBackground | ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_NoFocusOnAppearing | ImGuiWindowFlags_NoBringToFrontOnFocus )) { // colors for UI ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(1.f, 1.f, 1.f, 1.0f)); // 1 ImGui::PushStyleColor(ImGuiCol_PopupBg, ImVec4(0.14f, 0.14f, 0.14f, 0.9f)); ImGui::PushStyleColor(ImGuiCol_FrameBg, ImVec4(0.14f, 0.14f, 0.14f, 0.f)); ImGui::PushStyleColor(ImGuiCol_FrameBgHovered, ImVec4(0.24f, 0.24f, 0.24f, 0.46f)); ImGui::PushStyleColor(ImGuiCol_SliderGrab, ImVec4(0.85f, 0.85f, 0.85f, 0.86f)); ImGui::PushStyleColor(ImGuiCol_SliderGrabActive, ImVec4(0.95f, 0.95f, 0.95f, 1.00f)); ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.00f, 0.00f, 0.00f, 0.00f)); ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(0.4f, 0.4f, 0.4f, 0.56f)); ImGui::PushStyleColor(ImGuiCol_Header, ImVec4(0.36f, 0.36f, 0.36f, 0.9f)); ImGui::PushStyleColor(ImGuiCol_HeaderHovered, ImVec4(0.36f, 0.36f, 0.36f, 0.5f)); ImGui::PushStyleColor(ImGuiCol_HeaderActive, ImVec4(0.88f, 0.88f, 0.88f, 0.73f)); ImGui::PushStyleColor(ImGuiCol_Tab, ImVec4(0.83f, 0.83f, 0.84f, 0.78f)); ImGui::PushStyleColor(ImGuiCol_TabHovered, ImVec4(0.43f, 0.43f, 0.43f, 0.60f)); ImGui::PushStyleColor(ImGuiCol_TabActive, ImVec4(0.40f, 0.40f, 0.40f, 1.00f)); // 14 colors // // Buttons on top // // Disable output ImGuiToolkit::ButtonToggle(ICON_FA_EYE_SLASH, &Settings::application.render.disabled, MENU_OUTPUTDISABLE); // Add / Remove windows ImGui::SameLine(); if ( Settings::application.num_output_windows < MAX_OUTPUT_WINDOW) { if (ImGuiToolkit::IconButton(18, 4, "More windows")) { ++Settings::application.num_output_windows; current_window_ = Settings::application.num_output_windows-1; } } else ImGuiToolkit::Icon(18, 4, false); ImGui::SameLine(); if ( Settings::application.num_output_windows > 0 ) { if (ImGuiToolkit::IconButton(19, 4, "Less windows")) { --Settings::application.num_output_windows; current_window_ = -1; } } else ImGuiToolkit::Icon(19, 4, false); // Modify current window if (current_window_ > -1) { ImGuiContext& g = *GImGui; // title output ImGui::SameLine(0, 5.f * g.Style.FramePadding.x); ImGui::Text("Output %d", current_window_ + 1); // Output options ImGui::SameLine(0, 2.f * g.Style.FramePadding.x); ImGuiToolkit::ButtonIconToggle(8,5,9,5, &Settings::application.windows[1+current_window_].scaled, "Window fit"); ImGui::SameLine(0, g.Style.FramePadding.x); ImGuiToolkit::ButtonIconToggle(10,1,11,1, &Settings::application.windows[1+current_window_].show_pattern, "Test pattern"); // White ballance color button static DialogToolkit::ColorPickerDialog whitebalancedialog; ImGui::SameLine(0, 1.5f * g.Style.FramePadding.x); ImGuiToolkit::PushFont(ImGuiToolkit::FONT_DEFAULT); ImGuiWindow* window = ImGui::GetCurrentWindow(); window->DC.CursorPos.y += g.Style.FramePadding.y; // hack to re-align color button to text if (ImGui::ColorButton("White balance", ImVec4(Settings::application.windows[1+current_window_].whitebalance.x, Settings::application.windows[1+current_window_].whitebalance.y, Settings::application.windows[1+current_window_].whitebalance.z, 1.f), ImGuiColorEditFlags_NoAlpha)) { if ( DialogToolkit::ColorPickerDialog::busy()) { Log::Warning("Close previously openned color picker."); } else { whitebalancedialog.setRGB( std::make_tuple(Settings::application.windows[1+current_window_].whitebalance.x, Settings::application.windows[1+current_window_].whitebalance.y, Settings::application.windows[1+current_window_].whitebalance.z) ); whitebalancedialog.open(); } } ImGui::PopFont(); // get icked color if dialog finished if (whitebalancedialog.closed()){ std::tuple c = whitebalancedialog.RGB(); Settings::application.windows[1+current_window_].whitebalance.x = std::get<0>(c); Settings::application.windows[1+current_window_].whitebalance.y = std::get<1>(c); Settings::application.windows[1+current_window_].whitebalance.z = std::get<2>(c); } // White ballance temperature ImGui::SameLine(); window->DC.CursorPos.y -= g.Style.FramePadding.y; if (ImGui::Button(ICON_FA_THERMOMETER_HALF ICON_FA_SORT_DOWN )) ImGui::OpenPopup("temperature_popup"); ImGuiToolkit::PushFont(ImGuiToolkit::FONT_DEFAULT); if (ImGui::IsItemHovered()){ ImGui::BeginTooltip(); ImGui::Text("%d Kelvin", 4000 + (int) ceil(Settings::application.windows[1+current_window_].whitebalance.w * 5000.f)); ImGui::EndTooltip(); } if (ImGui::BeginPopup("temperature_popup", ImGuiWindowFlags_NoMove)) { // High Kelvin = blue ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(0.4f, 0.4f, 1.f, 1.0f)); ImGui::Text(" " ICON_FA_THERMOMETER_FULL ); ImGui::PopStyleColor(1); // Slider Temperature K ImGui::VSliderFloat("##Temperature", ImVec2(30,260), &Settings::application.windows[1+current_window_].whitebalance.w, 0.0, 1.0, ""); if (ImGui::IsItemHovered() || ImGui::IsItemActive() ) { ImGui::BeginTooltip(); ImGui::Text("%d K", 4000 + (int) ceil(Settings::application.windows[1+current_window_].whitebalance.w * 5000.f)); ImGui::EndTooltip(); } // High Kelvin = blue ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(1.f, 0.4f, 0.4f, 1.0f)); ImGui::Text(" " ICON_FA_THERMOMETER_EMPTY ); ImGui::PopStyleColor(1); ImGui::EndPopup(); } ImGui::PopFont(); } ImGui::PopStyleColor(14); // 14 colors ImGui::End(); } ImGui::PopFont(); // display popup menu if (show_window_menu_ && current_window_ > -1) { ImGui::OpenPopup( "DisplaysOutputContextMenu" ); show_window_menu_ = false; } if (ImGui::BeginPopup("DisplaysOutputContextMenu")) { ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(COLOR_WINDOW, 1.f)); ImGui::PushStyleColor(ImGuiCol_HeaderHovered, ImVec4(COLOR_MENU_HOVERED, 0.5f)); // FULLSCREEN selection: list of monitors int index = 1; std::map _monitors = Rendering::manager().monitors(); for (auto monitor_iter = _monitors.begin(); monitor_iter != _monitors.end(); ++monitor_iter, ++index) { bool _fullscreen = Settings::application.windows[current_window_+1].fullscreen && Settings::application.windows[current_window_+1].monitor == monitor_iter->first; std::string menutext = std::string( ICON_FA_TV " Fullscreen on Display ") + std::to_string(index); if (ImGui::MenuItem( menutext.c_str(), nullptr, _fullscreen )){ windows_[current_window_].monitor_ = monitor_iter->first; Rendering::manager().outputWindow(current_window_).setFullscreen( windows_[current_window_].monitor_ ); } } // WINDOW mode : set size bool _windowed = !Settings::application.windows[current_window_+1].fullscreen; if (ImGui::MenuItem( ICON_FA_WINDOW_MAXIMIZE " Window", nullptr, &_windowed)){ Rendering::manager().outputWindow(current_window_).exitFullscreen(); // not fullscreen on a monitor windows_[current_window_].monitor_ = ""; } ImGui::Separator(); bool _borderless = !Settings::application.windows[current_window_+1].decorated; if (ImGui::MenuItem( ICON_FA_SQUARE_FULL " Borderless", nullptr, &_borderless, _windowed)){ Rendering::manager().outputWindow(current_window_).setDecoration(!_borderless); } if (ImGui::MenuItem( ICON_FA_EXPAND_ALT " Reset aspect ratio" , nullptr, false, _windowed )){ // reset aspect ratio glm::ivec4 rect = windowCoordinates(current_window_); float ar = Mixer::manager().session()->frame()->aspectRatio(); if ( rect.p / rect.q > ar) rect.p = ar * rect.q; else rect.q = rect.p / ar; Rendering::manager().outputWindow(current_window_).setCoordinates( rect ); } if (ImGui::MenuItem( ICON_FA_RULER_COMBINED " Reset to pixel size", nullptr, false, _windowed )){ // reset resolution to 1:1 glm::ivec4 rect = windowCoordinates(current_window_); rect.p = Mixer::manager().session()->frame()->width(); rect.q = Mixer::manager().session()->frame()->height(); Rendering::manager().outputWindow(current_window_).setCoordinates( rect ); } if (ImGui::MenuItem( ICON_FA_EXPAND " Fit all Displays", nullptr, false, _windowed )){ Rendering::manager().outputWindow(current_window_).setDecoration(false); glm::ivec4 rect (INT_MAX, INT_MAX, 0, 0); std::map _monitors = Rendering::manager().monitors(); for (auto monitor_iter = _monitors.begin(); monitor_iter != _monitors.end(); ++monitor_iter) { rect.x = MIN(rect.x, monitor_iter->second.x); rect.y = MIN(rect.y, monitor_iter->second.y); rect.p = MAX(rect.p, monitor_iter->second.x+monitor_iter->second.p); rect.q = MAX(rect.q, monitor_iter->second.y+monitor_iter->second.q); } Rendering::manager().outputWindow(current_window_).setCoordinates( rect ); } ImGui::Separator(); if ( ImGui::MenuItem( ICON_FA_REPLY " Reset") ) { glm::ivec4 rect (0, 0, 800, 600); rect.p = Mixer::manager().session()->frame()->width(); rect.q = Mixer::manager().session()->frame()->height(); Rendering::manager().outputWindow(current_window_).setDecoration(true); Settings::application.windows[1+current_window_].show_pattern = false; Settings::application.windows[1+current_window_].scaled = false; Settings::application.windows[1+current_window_].whitebalance = glm::vec4(1.f, 1.f, 1.f, 0.5f); if (Settings::application.windows[current_window_+1].fullscreen) Rendering::manager().outputWindow(current_window_).exitFullscreen(); else Rendering::manager().outputWindow(current_window_).setCoordinates( rect ); } ImGui::PopStyleColor(2); ImGui::EndPopup(); } draw_pending_ = false; } std::pair DisplaysView::pick(glm::vec2 P) { // prepare empty return value std::pair pick = { nullptr, glm::vec2(0.f) }; // mode placement output window if ( display_action_ == 0 ) { // get picking from generic View pick = View::pick(P); // test all windows current_window_ = -1; for (int i = 0; i < Settings::application.num_output_windows; ++i) { // ignore pick on render surface: it's the same as output surface if (pick.first == windows_[i].render_ || pick.first == windows_[i].fullscreen_ || pick.first == windows_[i].title_ ) pick.first = windows_[i].surface_; // detect clic on menu if (pick.first == windows_[i].menu_) show_window_menu_ = true; // activate / deactivate window if clic on any element of it if ( (pick.first == windows_[i].surface_) || (pick.first == windows_[i].handles_) || (pick.first == windows_[i].menu_) ) { current_window_ = i; windows_[i].overlays_->setActive(1); } else windows_[i].overlays_->setActive(0); } // ignore anything else than selected window if (current_window_ < 0) pick.first = nullptr; } // mode other else // if ( display_action_ == 1 ) { } return pick; } bool DisplaysView::canSelect(Source *) { return false; } void DisplaysView::select(glm::vec2 A, glm::vec2 B) { // unproject mouse coordinate into scene coordinates glm::vec3 scene_point_A = Rendering::manager().unProject(A); glm::vec3 scene_point_B = Rendering::manager().unProject(B); // select area in window placement mode if ( display_action_ == 0 ) { // picking visitor traverses the scene PickingVisitor pv(scene_point_A, scene_point_B, true); scene.accept(pv); // TODO Multiple window selection? if (!pv.empty()) { // find which window was picked auto itp = pv.rbegin(); for (; itp != pv.rend(); ++itp){ // search for WindowPreview auto w = std::find_if(windows_.begin(), windows_.end(), WindowPreview::hasNode(itp->first)); if (w != windows_.end()) { // cancel previous current if (current_window_>-1) windows_[current_window_].overlays_->setActive(0); // set current current_window_ = (int) std::distance(windows_.begin(), w); windows_[current_window_].overlays_->setActive(1); } } } } } void DisplaysView::initiate() { // initiate pending action if (!current_action_ongoing_ && current_window_ > -1) { // store status current_window_status_->copyTransform(windows_[current_window_].root_); // initiated current_action_ = ""; current_action_ongoing_ = true; } } void DisplaysView::terminate(bool force) { // terminate pending action if (current_action_ongoing_ || force) { if (current_window_ > -1) { if (Settings::application.windows[current_window_+1].fullscreen) { // Apply change of fullscreen monitor if ( windows_[current_window_].monitor_.compare(Settings::application.windows[current_window_+1].monitor) != 0 ) Rendering::manager().outputWindow(current_window_).setFullscreen( windows_[current_window_].monitor_ ); } else { // Apply coordinates to actual output window Rendering::manager().outputWindow(current_window_).setCoordinates( windowCoordinates(current_window_) ); } } // terminated current_action_ = ""; current_action_ongoing_ = false; // prevent next draw draw_pending_ = true; } } glm::ivec4 DisplaysView::windowCoordinates(int index) const { glm::ivec4 rect; rect.x = (windows_[index].root_->translation_.x - windows_[index].root_->scale_.x) / DISPLAYS_UNIT; rect.y = (windows_[index].root_->translation_.y + windows_[index].root_->scale_.y) / - DISPLAYS_UNIT; rect.p = 2.f * windows_[index].root_->scale_.x / DISPLAYS_UNIT; rect.q = 2.f * windows_[index].root_->scale_.y / DISPLAYS_UNIT; return rect; } std::string DisplaysView::fullscreenMonitor(int index) const { return windows_[index].monitor_; } View::Cursor DisplaysView::grab (Source *, glm::vec2 from, glm::vec2 to, std::pair pick) { std::ostringstream info; View::Cursor ret = Cursor(); // grab coordinates in scene-View reference frame glm::vec3 scene_from = Rendering::manager().unProject(from, scene.root()->transform_); glm::vec3 scene_to = Rendering::manager().unProject(to, scene.root()->transform_); glm::vec3 scene_translation = scene_to - scene_from; if ( current_window_ > -1 ) { // grab window not fullscreen : move or resizes if (!Settings::application.windows[current_window_+1].fullscreen) { // grab surface to move if ( pick.first == windows_[current_window_].surface_ ){ // apply translation windows_[current_window_].root_->translation_ = current_window_status_->translation_ + scene_translation; glm::ivec4 r = windowCoordinates(current_window_); // discretized translation with ALT if (UserInterface::manager().altModifier()) { r.x = ROUND(r.x, 0.01f); r.y = ROUND(r.y, 0.01f); windows_[current_window_].root_->translation_.x = (r.x * DISPLAYS_UNIT) + windows_[current_window_].root_->scale_.x; windows_[current_window_].root_->translation_.y = (r.y * - DISPLAYS_UNIT) - windows_[current_window_].root_->scale_.y; } // Show move cursor ret.type = Cursor_ResizeAll; info << "Window position " << r.x << ", " << r.y << " px"; } // grab handle to resize else if ( pick.first == windows_[current_window_].handles_ ){ // which corner was picked ? glm::vec2 corner = glm::round(pick.second); // transform from source center to corner glm::mat4 T = GlmToolkit::transform(glm::vec3(corner.x, corner.y, 0.f), glm::vec3(0.f, 0.f, 0.f), glm::vec3(1.f, 1.f, 1.f)); // transformation from scene to corner: glm::mat4 scene_to_corner_transform = T * glm::inverse(current_window_status_->transform_); glm::mat4 corner_to_scene_transform = glm::inverse(scene_to_corner_transform); // compute cursor movement in corner reference frame glm::vec4 corner_from = scene_to_corner_transform * glm::vec4( scene_from, 1.f ); glm::vec4 corner_to = scene_to_corner_transform * glm::vec4( scene_to, 1.f ); // operation of scaling in corner reference frame glm::vec3 corner_scaling = glm::vec3(corner_to) / glm::vec3(corner_from); glm::ivec4 rect; // RESIZE CORNER // proportional SCALING with SHIFT if (UserInterface::manager().shiftModifier()) { // calculate proportional scaling factor float factor = glm::length( glm::vec2( corner_to ) ) / glm::length( glm::vec2( corner_from ) ); // scale node windows_[current_window_].root_->scale_ = current_window_status_->scale_ * glm::vec3(factor, factor, 1.f); } // non-proportional CORNER RESIZE (normal case) else { // scale node windows_[current_window_].root_->scale_ = current_window_status_->scale_ * corner_scaling; } // discretized scaling with ALT if (UserInterface::manager().altModifier()) { // calculate ratio of scaling modulo the output resolution glm::vec3 outputsize = windows_[current_window_].root_->scale_ / DISPLAYS_UNIT; glm::vec3 framesize = Mixer::manager().session()->frame()->resolution(); glm::vec3 ra = outputsize / framesize; ra.x = ROUND(ra.x, 20.f); ra.y = ROUND(ra.y, 20.f); outputsize = ra * framesize; windows_[current_window_].root_->scale_.x = outputsize.x * DISPLAYS_UNIT; windows_[current_window_].root_->scale_.y = outputsize.y * DISPLAYS_UNIT; } // update corner scaling to apply to center coordinates corner_scaling = windows_[current_window_].root_->scale_ / current_window_status_->scale_; // TRANSLATION CORNER // convert source position in corner reference frame glm::vec4 center = scene_to_corner_transform * glm::vec4( current_window_status_->translation_, 1.f); // transform source center (in corner reference frame) center = glm::scale(glm::identity(), corner_scaling) * center; // convert center back into scene reference frame center = corner_to_scene_transform * center; // apply to node windows_[current_window_].root_->translation_ = glm::vec3(center); // rescale title bar windows_[current_window_].title_->scale_.y = WINDOW_TITLEBAR_HEIGHT / windows_[current_window_].root_->scale_.y; windows_[current_window_].title_->translation_.y = 1.f + windows_[current_window_].title_->scale_.y; // show cursor depending on diagonal (corner picked) T = glm::rotate(glm::identity(), current_window_status_->rotation_.z, glm::vec3(0.f, 0.f, 1.f)); T = glm::scale(T, current_window_status_->scale_); corner = T * glm::vec4( corner, 0.f, 0.f ); ret.type = corner.x * corner.y > 0.f ? Cursor_ResizeNESW : Cursor_ResizeNWSE; rect = windowCoordinates(current_window_); info << "Window size " << rect.p << " x " << rect.q << " px"; } } // grab fullscreen window : change monitor else { if ( pick.first == windows_[current_window_].surface_ ){ // convert mouse cursor coordinates to displays coordinates scene_to *= glm::vec3(1.f/DISPLAYS_UNIT, -1.f/DISPLAYS_UNIT, 1.f); // loop over all monitors std::map _monitors = Rendering::manager().monitors(); int index = 1; for (auto monitor_iter = _monitors.begin(); monitor_iter != _monitors.end(); ++monitor_iter, ++index) { // if the mouse cursor is over a monitor glm::ivec4 r = monitor_iter->second; if (scene_to.x > r.x && scene_to.x < r.x + r.p && scene_to.y > r.y && scene_to.y < r.y + r.q) { // show output frame on top of that monitor windows_[current_window_].root_->scale_.x = r.p * 0.5f * DISPLAYS_UNIT; windows_[current_window_].root_->scale_.y = r.q * 0.5f * DISPLAYS_UNIT; windows_[current_window_].root_->translation_.x = r.x * DISPLAYS_UNIT + windows_[current_window_].root_->scale_.x; windows_[current_window_].root_->translation_.y = -r.y * DISPLAYS_UNIT - windows_[current_window_].root_->scale_.y; // remember the output monitor selected windows_[current_window_].monitor_ = monitor_iter->first; // Show cursor ret.type = Cursor_Hand; info << "Fullscreen Monitor " << index << " (" << windows_[current_window_].monitor_ << ")\n "<< r.p << " x " << r.q << " px"; } } } } } // update cursor ret.info = info.str(); return ret; } View::Cursor DisplaysView::over (glm::vec2 pos) { // output_zoomarea_->visible_ = false; // if ( display_action_ == 2 ) { // // utility // bool _over_render = false; // glm::vec2 _uv_cursor_center(0.f, 0.f); // glm::vec3 scene_pos = Rendering::manager().unProject(pos); // // find if the output_render surface is under the cursor at scene_pos // _over_render = set_UV_output_render_from_pick(scene_pos, &_uv_cursor_center); // // set coordinates of zoom area indicator under cursor at position 'pos' // output_zoomarea_->translation_ = glm::inverse(scene.root()->transform_) * glm::vec4(scene_pos, 1.f); // output_zoomarea_->visible_ = _over_render; // output_zoomarea_->scale_ = glm::vec3(output_scale * output_->scale_.x, output_scale * output_->scale_.x, 1.f); // // mouse cursor is over the rendering surface // if (_over_render) { // // get the imgui screen area to render the zoom window into // const ImGuiIO& io = ImGui::GetIO(); // const ImVec2 margin(100.f, 50.f); // ImVec2 screen = io.DisplaySize - margin * 2.f; // ImVec2 win_pos = margin; // ImVec2 win_size = screen * 0.5f; // // vimix window is horizontal // if ( screen.x > screen.y){ // // set zoom window to square // win_size.y = win_size.x; // // show the zoom window left and right of window // win_pos.y += (screen.y - win_size.y) * 0.5f; // win_pos.x += (pos.x > io.DisplaySize.x * 0.5f) ? 0 : win_size.x; // } // // vimix window is vertical // else { // // set zoom window to square // win_size.x = win_size.y; // // show the zoom window top and bottom of window // win_pos.x += (screen.x - win_size.x) * 0.5f; // win_pos.y += (pos.y > io.DisplaySize.y * 0.5f) ? 0 : win_size.y; // } // // DRAW window without decoration, 100% opaque, with 2 pixels on the border // ImGui::PushStyleVar(ImGuiStyleVar_Alpha, 1.f); // ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 0.f); // ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(2.f, 2.f)); // ImGui::SetNextWindowPos(win_pos, ImGuiCond_Always); // ImGui::SetNextWindowSize(win_size, ImGuiCond_Always); // if (ImGui::Begin("##DisplaysMacro", NULL, ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoDecoration // | ImGuiWindowFlags_NoSavedSettings )) // { // ImVec2 _zoom(zoom_factor, zoom_factor * output_ar); // ImVec2 _uv1( _uv_cursor_center.x - _zoom.x * 0.5f, _uv_cursor_center.y - _zoom.y * 0.5f); // _uv1 = ImClamp( _uv1, ImVec2(0.f, 0.f), ImVec2(1.f, 1.f)); // ImVec2 rect = _uv1 + _zoom; // ImVec2 _uv2 = ImClamp( rect, ImVec2(0.f, 0.f), ImVec2(1.f, 1.f)); // _uv1 = _uv2 - _zoom; // ImGui::Image((void*)(intptr_t) output_render_->textureIndex(), win_size, _uv1, _uv2); // ImGui::End(); // } // ImGui::PopStyleVar(3); // } // } return Cursor(); } bool DisplaysView::doubleclic (glm::vec2 P) { // TODO find which window? if ( pick(P).first != nullptr) { Rendering::manager().outputWindow(current_window_).show(); return true; } return false; } void DisplaysView::arrow (glm::vec2 movement) { // grab only works on current window if not fullscreen if (current_window_ > -1 && !Settings::application.windows[current_window_+1].fullscreen) { // operate in pixel coordinates static glm::vec2 p; // initiate movement (only once) if (!current_action_ongoing_) { // initial position p.x = (windows_[current_window_].root_->translation_.x - windows_[current_window_].root_->scale_.x) / DISPLAYS_UNIT; p.y = (windows_[current_window_].root_->translation_.y + windows_[current_window_].root_->scale_.y) / - DISPLAYS_UNIT; // initiate (terminated at key release) current_action_ongoing_ = true; } // add movement vector to position (pixel precision) p += movement ; //* (dt_ * 0.5f); // discretized translation with ALT if (UserInterface::manager().altModifier()) { glm::vec2 q; q.x = ROUND(p.x, 0.05f); // 20 pix precision q.y = ROUND(p.y, 0.05f); // convert back to output-frame coordinates windows_[current_window_].root_->translation_.x = (q.x * DISPLAYS_UNIT) + windows_[current_window_].root_->scale_.x; windows_[current_window_].root_->translation_.y = (q.y * - DISPLAYS_UNIT) - windows_[current_window_].root_->scale_.y; } else { // convert back to output-frame coordinates windows_[current_window_].root_->translation_.x = (p.x * DISPLAYS_UNIT) + windows_[current_window_].root_->scale_.x; windows_[current_window_].root_->translation_.y = (p.y * - DISPLAYS_UNIT) - windows_[current_window_].root_->scale_.y; } } } bool WindowPreview::hasNode::operator()(WindowPreview elem) const { if (_n) { if (_n == elem.render_ || _n == elem.surface_ || _n == elem.title_) return true; } return false; } //// from scene coordinates pos, get the UV of point in the output render //// returns true if on output_render //bool DisplaysView::get_UV_window_render_from_pick(const glm::vec3 &pos, glm::vec2 *uv) //{ // bool ret = false; // // perform picking at pos // PickingVisitor pv(pos); // scene.accept(pv); // // find if the output_render surface is at pos // for(std::vector< std::pair >::const_reverse_iterator p = pv.rbegin(); p != pv.rend(); ++p){ // // if found the output rendering surface at pos // if ( p->first == window_render_) { // // set return value to true // ret = true; // // convert picking coordinates into UV // // This works only as we are sure that the output_render_ UV mapping is fixed // *uv = (p->second + glm::vec2(1.f, -1.f)) * glm::vec2(0.5f, -0.5f); // break; // } // } // return ret; //}