#include "externals.hh" #include "ui-colorgrading.hh" #include "ui-utilities.hh" #define IMGUI_DEFINE_MATH_OPERATORS #include namespace ImGui { namespace { bool CGCModeButton_( char const* const name , const bool disabled ) { if ( disabled ) { PushDisabled( ); } const bool rv( Button( name ) ); if ( disabled ) { PopDisabled( ); } return rv; } } // namespace ImGui:: bool ColorSelectorBar( float* const value , const float base , const float unit , const ImVec4 color , char const* const label ) noexcept { const float BarWidth = 24.f; const float BarHeight = 180.f; const ImVec2 labelSize = CalcTextSize( label ); const ImVec2 maxValueSize = CalcTextSize( "-9.99" ); const float fullWidth{ std::max( BarWidth , std::max( labelSize.x , maxValueSize.x ) ) + 2 }; // Compute bounding boxes auto* const win( GetCurrentWindow( ) ); const ImVec2 cPos( win->DC.CursorPos ); const ImRect bbBar( cPos + ImVec2( ( fullWidth - BarWidth ) * .5f , 0 ) , cPos + ImVec2( ( fullWidth + BarWidth ) * .5f , BarHeight ) ); const ImRect bbLabel( cPos + ImVec2( 0 , BarHeight + 2 ) , cPos + ImVec2( fullWidth , BarHeight + labelSize.y + 2 ) ); const ImRect bbValue( ImVec2( cPos.x , bbLabel.Max.y + 2 ) , bbLabel.Max + ImVec2( 0 , maxValueSize.y + 2 ) ); const ImRect bbAll( cPos , bbValue.Max ); auto& style( GetStyle( ) ); auto id( win->GetID( label ) ); ItemSize( bbAll , style.FramePadding.y ); if ( !ItemAdd( bbAll , id ) ) { return false; } const bool tabFocus{ FocusableItemRegister( win , id ) }; const bool hovered{ ItemHoverable( bbBar , id ) }; auto* const ctx( GetCurrentContext( ) ); if ( tabFocus || ( hovered && ctx->IO.MouseClicked[ 0 ] ) ) { SetActiveID( id , win ); FocusWindow( win ); } float nValue{ *value }; const bool active{ ctx->ActiveId == id }; if ( active ) { if ( ctx->IO.MouseDown[ 0 ] ) { const float mPos{ ctx->IO.MousePos.y }; const float clickPos{ 1.0f - ImClamp( ( mPos - bbBar.Min.y - 1 ) / ( BarHeight - 2 ) , 0.0f , 1.0f ) }; nValue = base + unit * 2.f * clickPos; } else { ClearActiveID( ); } } else if ( hovered && ctx->IO.KeyCtrl && ctx->IO.MouseWheel != 0.f ) { nValue += unit * .01f * ctx->IO.MouseWheel; ctx->IO.MouseWheel = 0.f; } //- DRAW --------------------------------------------------------------- auto* const dl( GetWindowDrawList( ) ); const auto dispColor{ GetColorU32( ImVec4( color.x * ( ( hovered || active ) ? 1.f : .5f ) , color.y * ( ( hovered || active ) ? 1.f : .5f ) , color.z * ( ( hovered || active ) ? 1.f : .5f ) , 1 ) ) }; // Draw bar body const ImU32 bgCol{ GetColorU32( ( ctx->ActiveId == id || hovered ) ? ImVec4( .25f , .25f , .25f , 1.f ) : ImVec4( .1f , .1f , .1f , 1.f ) ) }; const ImU32 fCol{ GetColorU32( ( ctx->ActiveId == id || hovered ) ? ImVec4( 1.f , 1.f , 1.f , 1.f ) : ImVec4( 0.f , 0.f , 0.f , 1.f ) ) }; dl->AddRectFilled( bbBar.Min , bbBar.Max , bgCol ); dl->AddRect( bbBar.Min , bbBar.Max , fCol ); // Draw colored area on bar const float val( std::max( base , std::min( base + unit * 2 , nValue ) ) ); const float vy2( ( BarHeight - 2 ) * ( 1 - ( val - base ) / ( unit * 2 ) ) ); dl->AddRectFilled( bbBar.Min + ImVec2( 1 , BarHeight * .5 ) , bbBar.Min + ImVec2( BarWidth - 1 , 1 + vy2 ) , dispColor ); dl->AddLine( bbBar.Min + ImVec2( 1 , BarHeight * .5 ) , bbBar.Min + ImVec2( BarWidth - 1 , BarHeight * .5 ) , dispColor ); // Draw label & value const char* tStart = &ctx->TempBuffer[ 0 ]; const char* tEnd = tStart + ImFormatString( ctx->TempBuffer, IM_ARRAYSIZE( ctx->TempBuffer ), "%.2f" , *value ); PushStyleColor( ImGuiCol_Text , dispColor ); RenderTextClipped( bbLabel.Min , bbLabel.Max , label , nullptr , nullptr , ImVec2( .5f , .5f ) ); RenderTextClipped( bbValue.Min , bbValue.Max , tStart , tEnd , nullptr , ImVec2( .5f , .5f ) ); PopStyleColor( ); if ( nValue != *value ) { *value = val; return true; } return false; } bool HueSaturationPad( char const* const name , float* const hue , float* const saturation , const float size ) noexcept { // Check/set bounding box const auto wSize{ ImMax( size , CalcItemWidth( ) ) }; auto* const win( GetCurrentWindow( ) ); const ImVec2 cPos( win->DC.CursorPos ); const ImRect bb{ cPos , cPos + ImVec2( wSize , wSize ) }; auto& style( GetStyle( ) ); auto id{ win->GetID( name ) }; ItemSize( bb , style.FramePadding.y ); if ( !ItemAdd( bb , id ) ) { return false; } const ImVec2 wCenter{ cPos + ImVec2( wSize * .5f , wSize * .5f ) }; const float wThickness = wSize * 0.08f; const float wOuterRadius = wSize * 0.50f; const float wInnerRadius = wOuterRadius - wThickness; auto* const ctx( GetCurrentContext( ) ); const auto mPos{ ctx->IO.MousePos }; const auto rmPos{ mPos - wCenter }; const auto mcSqDist{ rmPos.x * rmPos.x + rmPos.y * rmPos.y }; const bool hovered{ ItemHoverable( bb , id ) && ( mcSqDist <= wSize * wSize * .25f ) }; const bool tabFocus{ FocusableItemRegister( win , id ) }; if ( tabFocus || ( hovered && ctx->IO.MouseClicked[ 0 ] ) ) { SetActiveID( id , win ); FocusWindow( win ); } const bool active{ ctx->ActiveId == id }; float nHue{ *hue } , nSat{ *saturation }; if ( active ) { if ( ctx->IO.MouseDown[ 0 ] ) { nHue = ( 1.f + atan2f( -rmPos.y , -rmPos.x ) / IM_PI ) * .5f; nSat = ImSaturate( sqrtf( mcSqDist ) / wInnerRadius ); } else { ClearActiveID( ); } } else if ( hovered && ctx->IO.KeyCtrl && ctx->IO.MouseWheel != 0.f ) { const auto change{ ctx->IO.MouseWheel * .005f }; if ( ctx->IO.KeyShift ) { nSat = ImSaturate( nSat + change ); } else { nHue = fmodf( nHue + change + 1.f , 1.f ); } } //- DRAW --------------------------------------------------------------- auto* const dl( GetWindowDrawList( ) ); const float aeps = 1.5f / wOuterRadius; // Half a pixel arc length in radians (2pi cancels out). const int segmentPerArc = ImMax( 4 , (int) wOuterRadius / 12 ); const ImU32 hue_colors[] = { IM_COL32( 255 , 0 , 0 , 255 ) , IM_COL32( 255 , 255 , 0 , 255 ) , IM_COL32( 0 , 255 , 0 , 255 ) , IM_COL32( 0 , 255 , 255 , 255 ) , IM_COL32( 0 , 0 , 255 , 255 ) , IM_COL32( 255 , 0 , 255 , 255 ) , IM_COL32( 255 , 0 , 0 , 255 ) }; const auto avgRadius( ( wInnerRadius + wOuterRadius ) * .5f ); // Pad background and cross const auto bgColor{ GetColorU32( ( hovered || active ) ? ImVec4( .4f , .4f , .4f , 1 ) : ImVec4( .25f , .25f , .25f , 1 ) ) }; dl->AddCircleFilled( wCenter , avgRadius , bgColor ); dl->AddLine( wCenter - ImVec2( 0 , avgRadius ) , wCenter + ImVec2( 0 , avgRadius ) , GetColorU32( ImVec4( .1 , .1 , .1 , 1 ) ) ); dl->AddLine( wCenter - ImVec2( avgRadius , 0 ) , wCenter + ImVec2( avgRadius , 0 ) , GetColorU32( ImVec4( .1 , .1 , .1 , 1 ) ) ); // Color wheel for ( int n = 0 ; n < 6 ; n ++ ) { const float a0 = ( n ) / 6.f * 2.f * IM_PI - aeps; const float a1 = ( n + 1.f ) / 6.f * 2.f * IM_PI + aeps; const int vStart = dl->_VtxCurrentIdx; dl->PathArcTo( wCenter , avgRadius , a0 , a1 , segmentPerArc ); dl->PathStroke( IM_COL32_WHITE , false, wThickness); // Paint colors over existing vertices const ImVec2 gradientP0( wCenter.x + cosf( a0 ) * wInnerRadius , wCenter.y + sinf( a0 ) * wInnerRadius ); const ImVec2 gradientP1( wCenter.x + cosf( a1 ) * wInnerRadius , wCenter.y + sinf( a1 ) * wInnerRadius ); ShadeVertsLinearColorGradientKeepAlpha( dl->_VtxWritePtr - ( dl->_VtxCurrentIdx - vStart ), dl->_VtxWritePtr , gradientP0 , gradientP1 , hue_colors[ n ] , hue_colors[ n + 1 ] ); } // Cursor from hue and saturation const float cAngle{ nHue * 2 * IM_PI }; const float cDist{ nSat * wInnerRadius }; ImVec4 cColor{ 0 , 0 , 0 , 1 }; ColorConvertHSVtoRGB( *hue , *saturation , 1 , cColor.x , cColor.y , cColor.z ); dl->AddCircleFilled( wCenter + ImVec2( cos( cAngle ) * cDist , sin( cAngle ) * cDist ) , ( wOuterRadius - wInnerRadius ) * .5f , GetColorU32( cColor ) ); dl->AddCircle( wCenter + ImVec2( cos( cAngle ) * cDist , sin( cAngle ) * cDist ) , ( wOuterRadius - wInnerRadius ) * .5f , GetColorU32( ImVec4( 1 , 1 , 1 , 1 ) ) ); // Update values if ( *hue != nHue || *saturation != nSat ) { *hue = nHue; *saturation = nSat; return true; } return false; } bool ColorGradingControls( char const* const name , float* const red , float* const green , float* const blue , const float base , const float unit ) noexcept { using namespace ImGui; assert( red && green && blue ); assert( unit > 0.f && "invalid unit" ); PushID( name ); BeginGroup( ); ImGuiWindow* const window{ GetCurrentWindow() }; ImGuiStorage* const storage{ window->DC.StateStorage }; const bool wheelMode{ storage->GetBool( window->GetID( name ) , true ) }; // Mode selection bool modeChanged{ false }; modeChanged = CGCModeButton_( "Color wheel" , wheelMode ); SameLine( ); modeChanged = CGCModeButton_( "Components" , !wheelMode ) || modeChanged; if ( modeChanged ) { storage->SetBool( window->GetID( name ) , !wheelMode ); } bool changed; if ( wheelMode ^ modeChanged ) { const float scRed { ImSaturate( ( *red - base ) / ( unit * 2 ) ) } , scGreen{ ImSaturate( ( *green - base ) / ( unit * 2 ) ) } , scBlue { ImSaturate( ( *blue - base ) / ( unit * 2 ) ) }; float hue , saturation , value; ColorConvertRGBtoHSV( scRed , scGreen , scBlue , hue , saturation , value ); PushMultiItemsWidths( 2 ); changed = HueSaturationPad( "" , &hue , &saturation , 180.f ); PopItemWidth( ); SameLine( 0 , 0 * GetStyle( ).ItemInnerSpacing.x ); ImVec4 updated{ 0 , 0 , 0 , 1 }; ColorConvertHSVtoRGB( hue , saturation * .5f , 1 , updated.x , updated.y , updated.z ); changed = ColorSelectorBar( &value , 0 , .5 , updated , "V" ) || changed; if ( changed ) { ColorConvertHSVtoRGB( hue , saturation , value , updated.x , updated.y , updated.z ); *red = updated.x * unit * 2 + base; *green = updated.y * unit * 2 + base; *blue = updated.z * unit * 2 + base; } } else { PushMultiItemsWidths( 3 ); changed = ColorSelectorBar( red , base , unit , ImVec4( 1 , 0 , 0 , 0 ) , "R" ); PopItemWidth( ); SameLine( 0 , GetStyle( ).ItemInnerSpacing.x ); changed = ColorSelectorBar( green , base , unit , ImVec4( 0 , 1 , 0 , 0 ) , "G" ) || changed; PopItemWidth( ); SameLine( 0 , GetStyle( ).ItemInnerSpacing.x ); changed = ColorSelectorBar( blue , base , unit , ImVec4( .3 , .3 , 1 , 0 ) , "B" ) || changed; } PopItemWidth( ); EndGroup( ); PopID( ); return changed; } } // namespace ImGui