#include "externals.hh" #include "c-camera.hh" #include "c-utilities.hh" #include "ui-utilities.hh" #define IMGUI_DEFINE_MATH_OPERATORS #include void ImGui::PushDisabled( ) noexcept { PushItemFlag( ImGuiItemFlags_Disabled , true ); PushStyleVar( ImGuiStyleVar_Alpha , GetStyle( ).Alpha * .5f ); } void ImGui::PopDisabled( ) noexcept { PopItemFlag( ); PopStyleVar( ); } /*------------------------------------------------------------------------------*/ bool ImGui::MenuItemCheckbox( char const* name , bool* checked ) noexcept { bool rv{ MenuItem( name , "" , *checked , true ) }; if ( rv ) { *checked = !*checked; } return rv; } /*------------------------------------------------------------------------------*/ bool ImGui::ToolbarButton( char const* const string , ImVec2 const& size , char const* const tooltip , bool enabled , bool toggled ) noexcept { if ( !enabled ) { PushDisabled( ); } if ( toggled ) { PushStyleColor( ImGuiCol_Border , 0x7fffffff ); PushStyleColor( ImGuiCol_BorderShadow , 0 ); PushStyleVar( ImGuiStyleVar_FrameBorderSize , 1 ); } const bool rv{ Button( string , size ) }; if ( toggled ) { PopStyleVar( ); PopStyleColor( 2 ); } if ( !enabled ) { PopDisabled( ); } else if ( tooltip && IsItemHovered( ) ) { BeginTooltip( ); Text( tooltip ); EndTooltip( ); } return rv; } void ImGui::ToolbarSeparator( ) noexcept { SameLine( ); VerticalSeparator( ); SameLine( ); } /*------------------------------------------------------------------------------*/ uint32_t ImGui::ColorHSVAToU32( float hue , float saturation , float value , float alpha ) noexcept { ImVec4 out{ 0, 0, 0, alpha }; ColorConvertHSVtoRGB( hue , saturation , value , out.x , out.y , out.z ); return GetColorU32( out ); } /*------------------------------------------------------------------------------*/ // Code stolen and modified from ImGui itself bool ImGui::UserScrollbar( const bool horizontal , const float total , const float display , float* const scrollPos , const ImVec2 topLeft , const float length , char const* const name ) noexcept { ImGuiContext& g = *GImGui; ImGuiWindow* const window = g.CurrentWindow; ImDrawList* const dl = window->DrawList; const ImGuiStyle& style = g.Style; const ImGuiID id = window->GetID( name ? name : ( horizontal ? "#UserScrollX" : "#UserScrollY" ) ); // Render background ImRect bb = horizontal ? ImRect( topLeft , topLeft + ImVec2( length , style.ScrollbarSize ) ) : ImRect( topLeft , topLeft + ImVec2( style.ScrollbarSize , length ) ); ImRect contentArea = window->ContentsRegionRect; contentArea.Translate( window->Pos ); if ( !bb.Overlaps( contentArea ) ) { return false; } dl->AddRectFilled( bb.Min , bb.Max , GetColorU32( ImGuiCol_ScrollbarBg ) ); bb.Expand( ImVec2( -ImClamp( (float)(int)( ( bb.Max.x - bb.Min.x - 2.0f ) * 0.5f ) , 0.0f , 3.0f ) , -ImClamp( (float)(int)( ( bb.Max.y - bb.Min.y - 2.0f ) * 0.5f ) , 0.0f , 3.0f ) ) ); // V denote the main, longer axis of the scrollbar (= height for a // vertical scrollbar) const float scrollbarSizeV = horizontal ? bb.GetWidth( ) : bb.GetHeight( ); float scrollV = *scrollPos; // Calculate the height of our grabbable box. It generally represent // the amount visible (vs the total scrollable amount) But we maintain // a minimum size in pixel to allow for the user to still aim inside. const float maxSize = ImMax( ImMax( total , display ) , 1.0f ); const float grabSizePx = ImClamp( scrollbarSizeV * ( display / maxSize ) , style.GrabMinSize , scrollbarSizeV ); const float grabSizeNorm = grabSizePx / scrollbarSizeV; // Handle input right away. bool held = false; bool hovered = false; const bool previouslyHeld = ( g.ActiveId == id ); ButtonBehavior( bb , id , &hovered , &held ); const float scrollMax = ImMax( 1.0f , total - display ); float scrollRatio = ImSaturate( scrollV / scrollMax ); float grabNorm = scrollRatio * ( scrollbarSizeV - grabSizePx ) / scrollbarSizeV; if ( held && grabSizeNorm < 1.0f ) { ImGuiStorage* const storage = window->DC.StateStorage; const float scrollbarPosV = horizontal ? bb.Min.x : bb.Min.y; const float mousePos = horizontal ? g.IO.MousePos.x : g.IO.MousePos.y; float clickDeltaToGrabCenter = storage->GetFloat( id ); // Click position in scrollbar normalized space (0.0f->1.0f) const float clickedVNorm = ImSaturate( ( mousePos - scrollbarPosV ) / scrollbarSizeV ); SetHoveredID( id ); bool seekAbsolute = false; if ( !previouslyHeld ) { // On initial click calculate the distance between mouse // and the center of the grab if ( clickedVNorm >= grabNorm && clickedVNorm <= grabNorm + grabSizeNorm ) { clickDeltaToGrabCenter = clickedVNorm - grabNorm - grabSizeNorm * 0.5f; } else { seekAbsolute = true; clickDeltaToGrabCenter = 0.0f; } } // Apply scroll const float scrollVNorm = ImSaturate( ( clickedVNorm - clickDeltaToGrabCenter - grabSizeNorm * 0.5f ) / ( 1.0f - grabSizeNorm ) ); scrollV = (float)(int)( 0.5f + scrollVNorm * scrollMax ); // Update values for rendering scrollRatio = ImSaturate( scrollV / scrollMax ); grabNorm = scrollRatio * ( scrollbarSizeV - grabSizePx ) / scrollbarSizeV; // Update distance to grab now that we have seeked and saturated if ( seekAbsolute ) { clickDeltaToGrabCenter = clickedVNorm - grabNorm - grabSizeNorm * 0.5f; } storage->SetFloat( id , clickDeltaToGrabCenter ); } // Render const ImU32 grabCol = GetColorU32( held ? ImGuiCol_ScrollbarGrabActive : hovered ? ImGuiCol_ScrollbarGrabHovered : ImGuiCol_ScrollbarGrab); ImRect grabRect; if ( horizontal ) { grabRect = ImRect( ImLerp( bb.Min.x , bb.Max.x , grabNorm ) , bb.Min.y, ImMin( ImLerp( bb.Min.x , bb.Max.x , grabNorm ) + grabSizePx , contentArea.Max.x ) , bb.Max.y ); } else { grabRect = ImRect( bb.Min.x, ImLerp( bb.Min.y , bb.Max.y , grabNorm ) , bb.Max.x , ImMin( ImLerp( bb.Min.y , bb.Max.y , grabNorm ) + grabSizePx , contentArea.Max.y ) ); } dl->AddRectFilled( grabRect.Min , grabRect.Max , grabCol , style.ScrollbarRounding ); if ( scrollV != *scrollPos ) { *scrollPos = scrollV; return true; } return false; } /*= T_CameraMouseControl =======================================================*/ void T_CameraMouseControl::handleDragAndDrop( ImVec2 const& move , T_KbdMods modifiers , T_MouseButtons buttons ) noexcept { if ( move.x == 0 || move.y == 0 ) { return; } const float fdx( move.x * .1f * ( ( modifiers & E_KbdMod::CTRL ) ? 1.f : .1f ) ); const float fdy( move.y * .1f * ( ( modifiers & E_KbdMod::CTRL ) ? 1.f : .1f ) ); if ( ( buttons & E_MouseButton::LEFT ) && ( modifiers & E_KbdMod::SHIFT ) ) { // Left mouse button, shift - move camera const auto side( normalize( cross( camera.upVector( ) , camera.direction( ) ) ) ); camera.lookAt( ) += .1f * ( side * fdx + camera.upVector( ) * fdy ); } else if ( buttons & E_MouseButton::LEFT ) { // Left mouse button, no shift - change yaw/pitch updateAngle( camera.angles( ).y , fdx ); updateAngle( camera.angles( ).x , fdy ); } else if ( buttons & E_MouseButton::RIGHT ) { // Right mouse button - change roll updateAngle( camera.angles( ).z , fdx ); } camera.cvtAnglesToVectors( ); } void T_CameraMouseControl::handleWheel( const float wheel , T_KbdMods modifiers , T_MouseButtons /* buttons */ ) noexcept { const float delta( wheel * ( ( modifiers & E_KbdMod::CTRL ) ? 1.f : .1f) ); if ( modifiers & E_KbdMod::SHIFT ) { camera.fieldOfView( ) = ImClamp( camera.fieldOfView( ) + delta , 1.f , 179.f ); camera.cvtFov2Np( ); } else { camera.distance( ) = std::max( .01f , camera.distance( ) - delta ); camera.cvtAnglesToVectors( ); } } /*----------------------------------------------------------------------------*/ T_CameraChanges CameraUI( T_Camera& camera ) noexcept { using namespace ImGui; // What gets changed static const T_CameraChanges changeFlags[] = { E_CameraChange::MATRIX , {} , E_CameraChange::MATRIX , E_CameraChange::MATRIX , {} , E_CameraChange::MATRIX , E_CameraChange::MATRIX , {} , E_CameraChange::FOV , E_CameraChange::FOV , }; // Which update gets called. enum E_ChangeCall_ { CC_NONE , CC_FROM_VECTORS , CC_FROM_ANGLES , CC_FROM_FOV , CC_FROM_NP , }; static const E_ChangeCall_ changeCalls[] = { CC_FROM_ANGLES , CC_NONE , CC_FROM_ANGLES , CC_FROM_ANGLES , CC_NONE , CC_FROM_VECTORS , CC_FROM_VECTORS , CC_NONE , CC_FROM_FOV , CC_FROM_NP , }; static_assert( IM_ARRAYSIZE( changeFlags ) == IM_ARRAYSIZE( changeCalls ) , "invalid configuration" ); // Changes (also draws the fields) const bool changed[] = { DragFloat3( "Target" , &camera.lookAt( ).x ) , ( Separator( ) , false ) , DragFloat( "Distance" , &camera.distance( ) , .1f , .1f , 1e8 , "%.1f" ) , DragFloat3( "Angles" , &camera.angles( ).x , .01f , -180 , 180 ) , ( Separator( ) , false ) , DragFloat3( "Position" , &camera.position( ).x ) , DragFloat3( "Up vector" , &camera.upVector( ).x ) , ( Separator( ) , false ) , DragFloat( "FoV" , &camera.fieldOfView( ) , .01f , .01f , 179.9f ) , DragFloat( "Near plane" , &camera.nearPlane( ) , .00001f , .001f , 1e6f ) , }; static_assert( IM_ARRAYSIZE( changeFlags ) == IM_ARRAYSIZE( changed ) , "invalid configuration" ); T_CameraChanges changes; for ( unsigned i = 0 ; i < IM_ARRAYSIZE( changed ) ; i ++ ) { if ( changed[ i ] ) { switch ( changeCalls[ i ] ) { case CC_NONE: break; case CC_FROM_VECTORS: camera.cvtVectorsToAngles( ); break; case CC_FROM_ANGLES: camera.cvtAnglesToVectors( ); break; case CC_FROM_FOV: camera.cvtFov2Np( ); break; case CC_FROM_NP: camera.cvtNp2Fov( ); break; } changes = changes | changeFlags[ i ]; } } return changes; }