From 811b270baea3000e0507e665e5194a43708bcd40 Mon Sep 17 00:00:00 2001 From: Bruno Herbelin Date: Sun, 12 Mar 2023 00:31:41 +0100 Subject: [PATCH] Implementation of Custom Output area in Window Displays View Changed the 'Scaled' mode of window draw to allow custom centering and scaling of the output framebuffer in the window. Use DisplaysView to grab handles of the output frame. Save all windows custom output scaling in Settings. --- rsc/images/icons.dds | Bin 1638528 -> 1638528 bytes src/DisplaysView.cpp | 390 ++++++++++++++++++++++++++------------- src/DisplaysView.h | 23 ++- src/GlmToolkit.cpp | 8 + src/GlmToolkit.h | 1 + src/RenderingManager.cpp | 22 ++- src/Settings.cpp | 30 ++- src/Settings.h | 5 +- 8 files changed, 328 insertions(+), 151 deletions(-) diff --git a/rsc/images/icons.dds b/rsc/images/icons.dds index 1bd6f5f7868fef5c7aa7239c28c145b3661844be..f1a32a3bc96eb8379903f48815783a5eb933e601 100644 GIT binary patch delta 2092 zcma)6ZA@EL7(VB=+yZUk_MSpS4{FuOT|b|1k7ClD6LSV95CWX?gE zv26aZYwnL%oPnY-Ed1E0!JHo&a4u#uTQ)|P)Mx^W!>M5dmTaU9%kkWn+ZLmfbNZxx zPoL*K?|JWYkGt!Dn}3FJMmf%d%pH=8eprs%hyu>_G^ZM$A`oc7n-#y)Ny`r7A)@%4 zyLD&7HtOiag9LJ3(wC*r8ahxGVtfqpfoYk7UnZ$T+QNczy~}M^qvfHpn3t@m&7JicGrqH-@ z!nsfUc-Gd5v*4l$H^bgAegOitLMN3%xK+kOa6DDmwq{nXCsIs=oE$#_IQQsVsM9FC zMG{yYEUOb6Vk`vLZzR1-9gho5B&ma~Vfz&^uM4+8X|b>t_LiG2^S0UPmJNbi#=T(4 z76{zfBgjhT)fdww>FY~N+WvN0T5ldEHi*z@g*N zQ>!cK_|Mo5uw5KW5wrx<-EM6c=x>#xUo!d8l(v2WeEY>Q@c9YRrJ^e8$Q3Iwy-+KT zSvHKaK0f(SJ8-PswI1dMwF(dMf4-G zXmK-gp*IkQ>TktO$~~);xZ(?Od(zHFSNf)5LpJc=Py?Pr}J{8Rbq^FZD&_5y8avv1Jmd$zz z{53cST6^^c1OBt(|1rLQ6eGoWO0{$gZ4HVA@_+B8@s!rhcIulJ(;va*by5G4_&|Pk zGH#7YVNz(sD1Pn`3ZZx6v3mYRc3=7ZtLEZS_NvwnFK?BDw^xhpVS8Be#A9_m%zXik zz53^AzigP33;*d2gg)TznAcJu)QCp8KeYHbTzANIB|WM`;)#2nS{d`mBP~jcnhK#( zZWUL=CkgR9w?6UQCRQ7@ zSw!Bh@=wO(8>8|wnw-B1>>)Rqdhk-}-(gtgR}E4py^T!Vol znInZGmBUEq8ce}$a4}%|g?>J0B1E2LSGCM+rx)AMAOvlO*I>YAc+57;q!4Q5w=ExP zZR+ zoNPz#=!~iLEIRpux~b1yr}|z>HlpaH+i#m1N!GvWPBR8S9n@9o4xv8iuEF^Us)t;a ztf}}V|2h;p%n}T36(TQI;s$BvRa0|UPc2zJ^%e6zVq3mFw8rRplf7={QA^+0hej*d zjQW>$y;M}pB*gac?xp20(naGIV<0 zB=w+rDKs2Qe+SBr=~`%@%`6c(pCnFp`#olkG>&u*iNnGna}W+IM+QeGhmHC@SvQaT E4Ty{f?EnA( delta 1890 zcmcIkZA@EL7{2Gy-ty6Q_x83hnUFk_%U_G4ORA~VLMEK{Z~v0#{C)!yTK?xp3!?WgDT zY0vY1o%fvg?dj9n`rz{!@-GfK=h3TJJA%py-jDn;mS&ua zzMuxM)D5=Uizq4sRdDTFD2%mn6d)tLD9mJG64cR78OJ|FM{y$L5|8f-p6Z>zN+-;67o`D09e zfdoVRX%C)_@RFFJg)s`F4x^)dAG4Y!>Ouav2VZ-gKNZ(Hc|Z}sMzl`5??-$hBv9h> zmxg)r%Wfed;{Jm|0H2w+e~DWU2~|Y)mnw*S)gnwwkFMDP$h#R^#>RwfM@ol>IT)+> z2b?A@6}Wy`+xK`OP0Quv@J4PB6-@pU;tQ%6im{?+D@u-Z30q}+b+5bf#3x8Bj#4GZ zea$KO*FU8eG0jZO=#I4QDbA+9vTYM~KH~O~*nkj_6{cPu%nFX+&77e*CSp9sXZ>`{ zGGH7|B%=2kvU&Z_tfj*Bd-67x7snxPt2p+4-YQT%GG@9gM^%iV@vM=i_?a1D2hmRo zE?G}NSB5{A?}d7Y@xmJ}1;0$fC7 z=!USt5}5WRVF))p`c1ER-;GRLJ{|T;Ge*Y>KW8|ua}oXQUoEv}N!U@rKBpLBI<~?Q+s%L~6R{4S>yE87v6-qxxud|)?xY|_RL3`Mm4eNRl zSHTSyTq7@YMJyCv+Tq^vPK*yEhc-K8U4773MSUwy%tVs%JZgk<-1m~Y_Bfg1wEYn0 zD_Aj~)n2T#6hk#@5+aEP2a;>PVIi%-vb%IC&#Y|MbA#2crqI*3H(KxXlfK}sB+)&N zMH#oXI_@TtzqAv@=MOso#efn(DWHtZA9h}O0;TcAE%pwSBlV-s9=R9F#Ij=f(FwL1 zA7|f;x3~Yl{BH=47i(-DWRP9woe|leW*eHRq-e@W-Ad&-k0-mmUH6-Jo|k2*IVbX* z{Zg7E`@eG@kc$^n*2HI{C9nC`I`(GGNkVhZ7vu`C%Pi7G)0^#$xHpG^;P|*>x64jT zV8URUp}s(|<(lm!p)5G#kEgt?x_Zy{RdToM_VTJb$RECgyvcPyw%#8zH8X=-@a#n# zETs~97ycny^njF|VAsIz(n>Oi)ucO0CZBV`A1XKs&+p|=?aD*nGJSJ%-pXpD_53%c zHaCJ-k~DU?dSvLADV2wQ!L=9No$ #include + #include #include #include #include #include +//#include +//#include #include "ImGuiToolkit.h" #include "imgui_internal.h" @@ -64,18 +67,25 @@ DisplaysView::DisplaysView() : View(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; + // 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_); // surface background and texture - w->surface_ = new Surface; - w->root_->attach(w->surface_); + w->renderbuffer_ = new FrameBuffer(1024, 1024); // TODO delete on close w->shader_ = new ImageFilteringShader; w->shader_->setCode( _whitebalance.code().first ); - w->render_ = new Surface(w->shader_); - w->root_->attach(w->render_); + w->surface_ = new FrameBufferSurface(w->renderbuffer_, w->shader_); + w->root_->attach(w->surface_); + w->output_render_ = new Surface; // icon if disabled w->icon_ = new Handles(Handles::EYESLASHED); w->icon_->visible_ = false; @@ -91,6 +101,15 @@ DisplaysView::DisplaysView() : View(DISPLAYS) // overlays_ [1] is for active frame Group *g = new Group; w->overlays_->attach(g); + // Output frame + w->output_frame_ = new Group; + w->output_frame_->visible_ = false; + frame = new Frame(Frame::SHARP, Frame::THIN, Frame::NONE); + frame->color = glm::vec4( COLOR_FRAME, 1.f ); + w->output_frame_->attach(frame); + w->output_handles_ = new Handles(Handles::RESIZE); + w->output_handles_->color = glm::vec4( COLOR_FRAME, 1.f ); + w->output_frame_->attach(w->output_handles_); // Overlay menu icon w->menu_ = new Handles(Handles::MENU); w->menu_->color = glm::vec4( COLOR_WINDOW, 1.f ); @@ -99,6 +118,7 @@ DisplaysView::DisplaysView() : View(DISPLAYS) frame = new Frame(Frame::SHARP, Frame::LARGE, Frame::NONE); frame->color = glm::vec4( COLOR_WINDOW, 1.f ); g->attach(frame); + g->attach(w->output_frame_); // Overlay has two modes : window or fullscreen w->mode_ = new Switch; g->attach(w->mode_); @@ -111,12 +131,12 @@ DisplaysView::DisplaysView() : View(DISPLAYS) 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_); +// // 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); @@ -125,11 +145,9 @@ DisplaysView::DisplaysView() : View(DISPLAYS) // initial behavior: no window selected, no menu show_window_menu_ = false; current_window_ = -1; - current_window_status_ = new Group; + current_window_status_ = new Group; + current_output_status_ = new Group; draw_pending_ = false; - - // display actions : 0 = move output, 1 paint, 2 erase - display_action_ = 0; output_ar = 1.f; } @@ -141,8 +159,34 @@ void DisplaysView::update(float dt) 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() ); + for (int i = 0; i < MAX_OUTPUT_WINDOW; ++i) { + windows_[i].output_render_->setTextureIndex( Rendering::manager().outputWindow(i).texture() ); + + // update visible flag + windows_[i].root_->visible_ = i < Settings::application.num_output_windows; + windows_[i].icon_->visible_ = Settings::application.render.disabled; + + // Rendering of output is scaled to content and manipulated by output frame + if (Settings::application.windows[i+1].scaled) { + windows_[i].output_render_->scale_ = windows_[i].output_frame_->scale_; + windows_[i].output_render_->translation_ = windows_[i].output_frame_->translation_; + // show output frame + windows_[i].output_frame_->visible_ = true; + } + // Rendering of output is adjusted to match aspect ratio of framebuffer + else { + float out_ar = windows_[i].root_->scale_.x / windows_[i].root_->scale_.y; + if (output_ar < out_ar) + windows_[i].output_render_->scale_ = glm::vec3(output_ar / out_ar, 1.f, 1.f); + else + windows_[i].output_render_->scale_ = glm::vec3(1.f, out_ar / output_ar, 1.f); + // reset translation + windows_[i].output_render_->translation_ = glm::vec3(0.f); + // do not show output frame + windows_[i].output_frame_->visible_ = false; + } + + } output_ar = Mixer::manager().session()->frame()->aspectRatio(); } @@ -178,7 +222,7 @@ void DisplaysView::recenter () 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 + // Monitor color frame Frame *frame = new Frame(Frame::SHARP, Frame::THIN, Frame::GLOW); frame->color = glm::vec4( COLOR_MONITOR, 1.f); m->attach(frame); @@ -255,29 +299,17 @@ void DisplaysView::draw() 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; + // Render the output into the render buffer (displayed on the FrameBufferSurface surface_) + windows_[i].output_render_->update(0.f); + windows_[i].renderbuffer_->begin(); + windows_[i].output_render_->draw(glm::identity(), windows_[i].renderbuffer_->projection()); + windows_[i].renderbuffer_->end(); - 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; - } - } + // ensure the shader of the surface_ is configured + 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 ) { @@ -308,7 +340,6 @@ void DisplaysView::draw() ImGui::End(); } - } else { // output overlay for window @@ -367,6 +398,9 @@ void DisplaysView::draw() 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; } + + windows_[i].output_frame_->scale_ = Settings::application.windows[i+1].scale; + windows_[i].output_frame_->translation_ = Settings::application.windows[i+1].translation; } } @@ -405,7 +439,9 @@ void DisplaysView::draw() // // Disable output - ImGuiToolkit::ButtonToggle(ICON_FA_EYE_SLASH, &Settings::application.render.disabled, MENU_OUTPUTDISABLE); + ImGuiToolkit::ButtonToggle(ICON_FA_EYE_SLASH, &Settings::application.render.disabled); + if (ImGui::IsItemHovered()) + ImGuiToolkit::ToolTip(MENU_OUTPUTDISABLE, SHORTCUT_OUTPUTDISABLE); // Add / Remove windows ImGui::SameLine(); @@ -438,10 +474,10 @@ void DisplaysView::draw() // 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"); + ImGuiToolkit::ButtonIconToggle(9,5,9,5, &Settings::application.windows[1+current_window_].scaled, "Custom fit"); ImGui::SameLine(0, g.Style.FramePadding.x); - ImGuiToolkit::ButtonIconToggle(10,1,11,1, &Settings::application.windows[1+current_window_].show_pattern, "Test pattern"); + ImGuiToolkit::ButtonIconToggle(11,1,11,1, &Settings::application.windows[1+current_window_].show_pattern, "Test pattern"); // White ballance color button static DialogToolkit::ColorPickerDialog whitebalancedialog; @@ -523,8 +559,8 @@ void DisplaysView::draw() } if (ImGui::BeginPopup("DisplaysOutputContextMenu")) { - ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(COLOR_WINDOW, 1.f)); ImGui::PushStyleColor(ImGuiCol_HeaderHovered, ImVec4(COLOR_MENU_HOVERED, 0.5f)); + ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(COLOR_WINDOW, 1.f)); // FULLSCREEN selection: list of monitors int index = 1; @@ -555,25 +591,6 @@ void DisplaysView::draw() 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); @@ -589,6 +606,25 @@ void DisplaysView::draw() Rendering::manager().outputWindow(current_window_).setCoordinates( rect ); } + if (ImGui::MenuItem( ICON_FA_EXPAND_ALT " Restore 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 " Rescale 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 ); + } + ImGui::Separator(); if ( ImGui::MenuItem( ICON_FA_REPLY " Reset") ) { glm::ivec4 rect (0, 0, 800, 600); @@ -597,6 +633,8 @@ void DisplaysView::draw() 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_].scale = glm::vec3(1.f); + Settings::application.windows[1+current_window_].translation = glm::vec3(0.f); 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(); @@ -604,6 +642,16 @@ void DisplaysView::draw() Rendering::manager().outputWindow(current_window_).setCoordinates( rect ); } + if ( Settings::application.windows[current_window_+1].scaled ) { + ImGui::PopStyleColor(1); + ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(COLOR_FRAME, 1.f)); + + if ( ImGui::MenuItem( ICON_FA_VECTOR_SQUARE " Reset custom fit") ) { + Settings::application.windows[1+current_window_].scale = glm::vec3(1.f); + Settings::application.windows[1+current_window_].translation = glm::vec3(0.f); + } + } + ImGui::PopStyleColor(2); ImGui::EndPopup(); } @@ -616,47 +664,39 @@ 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); + // get picking from generic View + pick = View::pick(P); - // test all windows - current_window_ = -1; + // test all windows + current_window_ = -1; - for (int i = 0; i < Settings::application.num_output_windows; ++i) { + 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_; + // ignore pick on title: it's the same as output surface + if (pick.first == windows_[i].title_ || + pick.first == windows_[i].fullscreen_ ) + 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); + // 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].output_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 ) - { - } + // ignore anything else than selected window + if (current_window_ < 0) + pick.first = nullptr; return pick; } @@ -672,36 +712,34 @@ void DisplaysView::select(glm::vec2 A, glm::vec2 B) 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); + // picking visitor traverses the scene + PickingVisitor pv(scene_point_A, scene_point_B, true); + scene.accept(pv); - // TODO Multiple window selection? + // TODO Multiple window selection? - if (!pv.empty()) { + 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); + // 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); + windows_[current_window_].output_frame_->visible_ = false; } - + // set current + current_window_ = (int) std::distance(windows_.begin(), w); + windows_[current_window_].overlays_->setActive(1); + windows_[current_window_].output_frame_->visible_ = true; } } } - } void DisplaysView::initiate() @@ -709,8 +747,14 @@ void DisplaysView::initiate() // initiate pending action if (!current_action_ongoing_ && current_window_ > -1) { - // store status + // store status current window + // & make sure matrix transform of stored status is updated current_window_status_->copyTransform(windows_[current_window_].root_); + current_window_status_->update(0.f); + + // store status current output frame in current window + current_output_status_->copyTransform(windows_[current_window_].output_frame_); + current_output_status_->update(0.f); // initiated current_action_ = ""; @@ -735,6 +779,25 @@ void DisplaysView::terminate(bool force) Rendering::manager().outputWindow(current_window_).setCoordinates( windowCoordinates(current_window_) ); } + // test if output area is inside the Window (with a margin of 10%) + GlmToolkit::AxisAlignedBoundingBox _bb; + _bb.extend(glm::vec3(-1.f, -1.f, 0.f)); + _bb.extend(glm::vec3(1.f, 1.f, 0.f)); + GlmToolkit::AxisAlignedBoundingBox output_bb = _bb.transformed( windows_[current_window_].output_frame_->transform_ ); + _bb = _bb.scaled(glm::vec3(0.9f)); + if ( !_bb.intersect(output_bb) || output_bb.area() < 0.1f ) { + // No intersection of output bounding box with window area : revert to previous + windows_[current_window_].output_frame_->scale_ = Settings::application.windows[current_window_+1].scale; + windows_[current_window_].output_frame_->translation_ = Settings::application.windows[current_window_+1].translation; + } + else { + // Apply output area recentering to actual output window + Settings::application.windows[current_window_+1].scale = windows_[current_window_].output_frame_->scale_; + Settings::application.windows[current_window_+1].translation = windows_[current_window_].output_frame_->translation_; + } + + // reset overlay of grab corner + windows_[current_window_].output_handles_->overlayActiveCorner(glm::vec2(0.f)); } // terminated @@ -774,8 +837,89 @@ View::Cursor DisplaysView::grab (Source *, glm::vec2 from, glm::vec2 to, std::pa glm::vec3 scene_to = Rendering::manager().unProject(to, scene.root()->transform_); glm::vec3 scene_translation = scene_to - scene_from; + // a window is currently selected if ( current_window_ > -1 ) { + // Grab handles of the output frame to adjust + if ( pick.first == windows_[current_window_].output_handles_ ) { + + // which corner was picked ? + glm::vec2 corner = glm::round(pick.second); + // inform on which corner should be overlayed (opposite) + windows_[current_window_].output_handles_->overlayActiveCorner(-corner); + + // transform from 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)); + + glm::mat4 root_to_corner_transform = T * glm::inverse(current_output_status_->transform_); + glm::mat4 corner_to_root_transform = glm::inverse(root_to_corner_transform); + + // transformation from scene to corner: + glm::mat4 scene_to_corner_transform = root_to_corner_transform * glm::inverse( current_window_status_->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); + + // 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_].output_frame_->scale_ = current_output_status_->scale_ * glm::vec3(factor, factor, 1.f); + } + // non-proportional CORNER RESIZE (normal case) + else { + // scale node + windows_[current_window_].output_frame_->scale_ = current_output_status_->scale_ * corner_scaling; + } + + // update corner scaling to apply to center coordinates + corner_scaling = windows_[current_window_].output_frame_->scale_ / current_output_status_->scale_; + + // TRANSLATION CORNER + // convert source position in corner reference frame + glm::vec4 center = root_to_corner_transform * glm::vec4( current_output_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_root_transform * center; + // apply to node + windows_[current_window_].output_frame_->translation_ = glm::vec3(center); + + // discretized scaling with ALT + if (UserInterface::manager().altModifier()) { + windows_[current_window_].output_frame_->scale_ = glm::round( windows_[current_window_].output_frame_->scale_ * 20.f) * 0.05f; + windows_[current_window_].output_frame_->translation_ = glm::round( windows_[current_window_].output_frame_->translation_ * 20.f) * 0.05f; + } + + if (corner.x > 0.f) { + if (corner.y > 0.f) + info << "Top Right"; + else + info << "Bottom Right"; + } + else { + if (corner.y > 0.f) + info << "Top Left"; + else + info << "Bottom Left"; + } + + // show cursor depending on diagonal (corner picked) + T = glm::rotate(glm::identity(), current_output_status_->rotation_.z, glm::vec3(0.f, 0.f, 1.f)); + T = glm::scale(T, current_output_status_->scale_); + corner = T * glm::vec4( corner, 0.f, 0.f ); + ret.type = corner.x * corner.y > 0.f ? Cursor_ResizeNESW : Cursor_ResizeNWSE; + +// info << "Output resized " << std::fixed << std::setprecision(1) << 100.f * windows_[current_window_].output_frame_->scale_.x; +// info << " x " << 100.f * windows_[current_window_].output_frame_->scale_.y << " %"; + } + // grab window not fullscreen : move or resizes if (!Settings::application.windows[current_window_+1].fullscreen) { @@ -863,12 +1007,8 @@ View::Cursor DisplaysView::grab (Source *, glm::vec2 from, glm::vec2 to, std::pa 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; - + // show cursor + ret.type = Cursor_ResizeNWSE; rect = windowCoordinates(current_window_); info << "Window size " << rect.p << " x " << rect.q << " px"; } @@ -1053,7 +1193,7 @@ bool WindowPreview::hasNode::operator()(WindowPreview elem) const { if (_n) { - if (_n == elem.render_ || + if (_n == elem.fullscreen_ || _n == elem.surface_ || _n == elem.title_) return true; diff --git a/src/DisplaysView.h b/src/DisplaysView.h index 3178cf4..b44f046 100644 --- a/src/DisplaysView.h +++ b/src/DisplaysView.h @@ -6,11 +6,13 @@ struct WindowPreview { - Group *root_; - Group *status_; - Surface *surface_; - Surface *render_; + class FrameBuffer *renderbuffer_; class ImageFilteringShader *shader_; + class FrameBufferSurface *surface_; + Surface *output_render_; + Group *root_; + Group *output_frame_; + Handles *output_handles_; Switch *overlays_; Switch *mode_; Handles *handles_; @@ -21,12 +23,14 @@ struct WindowPreview std::string monitor_; WindowPreview() { - root_ = nullptr; - status_ = nullptr; + renderbuffer_ = nullptr; + shader_ = nullptr; surface_ = nullptr; - render_ = nullptr; + output_render_ = nullptr; + root_ = nullptr; + output_frame_ = nullptr; + output_handles_ = nullptr; overlays_ = nullptr; - status_ = nullptr; mode_ = nullptr; handles_ = nullptr; menu_ = nullptr; @@ -81,10 +85,9 @@ private: std::vector windows_; int current_window_; Group *current_window_status_; + Group *current_output_status_; bool show_window_menu_; - int display_action_; - // bool get_UV_window_render_from_pick(const glm::vec3 &pos, glm::vec2 *uv); diff --git a/src/GlmToolkit.cpp b/src/GlmToolkit.cpp index 04e5916..5fab992 100644 --- a/src/GlmToolkit.cpp +++ b/src/GlmToolkit.cpp @@ -238,6 +238,14 @@ GlmToolkit::AxisAlignedBoundingBox GlmToolkit::AxisAlignedBoundingBox::transform return bb; } +float GlmToolkit::AxisAlignedBoundingBox::area() const +{ + if (isNull()) + return 0.f; + + return (mMax.x - mMin.x) * (mMax.y - mMin.y) ; +} + bool GlmToolkit::operator< (const GlmToolkit::AxisAlignedBoundingBox& A, const GlmToolkit::AxisAlignedBoundingBox& B ) { if (A.isNull()) diff --git a/src/GlmToolkit.h b/src/GlmToolkit.h index 62241f6..75c8b65 100644 --- a/src/GlmToolkit.h +++ b/src/GlmToolkit.h @@ -24,6 +24,7 @@ public: inline glm::vec3 max() const { return mMax; } glm::vec3 center(bool ignore_z = true) const; glm::vec3 scale(bool ignore_z = true) const; + float area() const; bool intersect(const AxisAlignedBoundingBox& bb, bool ignore_z = true) const; bool contains(const AxisAlignedBoundingBox& bb, bool ignore_z = true) const; bool contains(glm::vec3 point, bool ignore_z = true) const; diff --git a/src/RenderingManager.cpp b/src/RenderingManager.cpp index 29391a5..721776d 100644 --- a/src/RenderingManager.cpp +++ b/src/RenderingManager.cpp @@ -1133,17 +1133,20 @@ bool RenderingWindow::draw(FrameBuffer *fb) shader_->uniforms_["Temperature"] = Settings::application.windows[index_].whitebalance.w; } - // calculate scaling factor of frame buffer inside window - const float windowAspectRatio = aspectRatio(); - const float renderingAspectRatio = fb->aspectRatio(); - glm::vec3 scale = glm::vec3(1.f, 1.f, 1.f); - // Display option: scaled or corrected aspect ratio - if (!Settings::application.windows[index_].scaled) { + if (Settings::application.windows[index_].scaled) { + surface_->scale_ = Settings::application.windows[index_].scale; + surface_->translation_ = Settings::application.windows[index_].translation; + } + else{ + // calculate scaling factor of frame buffer inside window + const float windowAspectRatio = aspectRatio(); + const float renderingAspectRatio = fb->aspectRatio(); if (windowAspectRatio < renderingAspectRatio) - scale = glm::vec3(1.f, windowAspectRatio / renderingAspectRatio, 1.f); + surface_->scale_ = glm::vec3(1.f, windowAspectRatio / renderingAspectRatio, 1.f); else - scale = glm::vec3(renderingAspectRatio / windowAspectRatio, 1.f, 1.f); + surface_->scale_ = glm::vec3(renderingAspectRatio / windowAspectRatio, 1.f, 1.f); + surface_->translation_ = glm::vec3(0.f); } // Display option: draw calibration pattern @@ -1168,7 +1171,8 @@ bool RenderingWindow::draw(FrameBuffer *fb) // actual render of the textured surface glBindTexture(GL_TEXTURE_2D, textureid_); static glm::mat4 projection = glm::ortho(-1.f, 1.f, -1.f, 1.f, -1.f, 1.f); - surface_->draw(glm::scale(glm::identity(), scale), projection); + surface_->update(0.f); + surface_->draw(glm::identity(), projection); // done drawing (unload shader from this glcontext) ShadingProgram::enduse(); diff --git a/src/Settings.cpp b/src/Settings.cpp index 9ef54a9..1511737 100644 --- a/src/Settings.cpp +++ b/src/Settings.cpp @@ -110,8 +110,16 @@ void Settings::Save(uint64_t runtime) window->SetAttribute("f", w.fullscreen); window->SetAttribute("s", w.scaled); window->SetAttribute("d", w.decorated); - window->SetAttribute("m", w.monitor.c_str()); - window->InsertEndChild( XMLElementFromGLM(&xmlDoc, w.whitebalance) ); + window->SetAttribute("m", w.monitor.c_str()); + XMLElement *tmp = xmlDoc.NewElement("whitebalance"); + tmp->InsertEndChild( XMLElementFromGLM(&xmlDoc, w.whitebalance) ); + window->InsertEndChild( tmp ); + tmp = xmlDoc.NewElement("scale"); + tmp->InsertEndChild( XMLElementFromGLM(&xmlDoc, w.scale) ); + window->InsertEndChild( tmp ); + tmp = xmlDoc.NewElement("translation"); + tmp->InsertEndChild( XMLElementFromGLM(&xmlDoc, w.translation) ); + window->InsertEndChild( tmp ); windowsNode->InsertEndChild(window); } @@ -515,11 +523,19 @@ void Settings::Load() int i = 0; windowNode->QueryIntAttribute("id", &i); if (i > 0) - w.name = APP_NAME " output " + std::to_string(i); + w.name = "Output " + std::to_string(i) + " - " APP_NAME; else w.name = APP_TITLE; - tinyxml2::XMLElementToGLM( windowNode->FirstChildElement("vec4"), w.whitebalance); + XMLElement *tmp = windowNode->FirstChildElement("whitebalance"); + if (tmp) + tinyxml2::XMLElementToGLM( tmp->FirstChildElement("vec4"), w.whitebalance); + tmp = windowNode->FirstChildElement("scale"); + if (tmp) + tinyxml2::XMLElementToGLM( tmp->FirstChildElement("vec3"), w.scale); + tmp = windowNode->FirstChildElement("translation"); + if (tmp) + tinyxml2::XMLElementToGLM( tmp->FirstChildElement("vec3"), w.translation); application.windows[i] = w; } @@ -549,11 +565,13 @@ void Settings::Load() application.views[id].name = viewNode->Attribute("name"); XMLElement* scaleNode = viewNode->FirstChildElement("default_scale"); - tinyxml2::XMLElementToGLM( scaleNode->FirstChildElement("vec3"), + if (scaleNode) + tinyxml2::XMLElementToGLM( scaleNode->FirstChildElement("vec3"), application.views[id].default_scale); XMLElement* translationNode = viewNode->FirstChildElement("default_translation"); - tinyxml2::XMLElementToGLM( translationNode->FirstChildElement("vec3"), + if (translationNode) + tinyxml2::XMLElementToGLM( translationNode->FirstChildElement("vec3"), application.views[id].default_translation); } diff --git a/src/Settings.h b/src/Settings.h index 6192894..1882e64 100644 --- a/src/Settings.h +++ b/src/Settings.h @@ -65,10 +65,13 @@ struct WindowConfig std::string monitor; bool show_pattern; glm::vec4 whitebalance; + glm::vec3 scale; + glm::vec3 translation; WindowConfig() : name(APP_TITLE), x(15), y(15), w(1280), h(720), fullscreen(false), scaled(false), decorated(true), - monitor(""), show_pattern(false), whitebalance(glm::vec4(1.f, 1.f, 1.f, 0.5f)) + monitor(""), show_pattern(false), whitebalance(glm::vec4(1.f, 1.f, 1.f, 0.5f)), + scale(glm::vec3(1.f)), translation(glm::vec3(0.f)) { } };