diff --git a/ui-sequencer.cc b/ui-sequencer.cc index ce24036..7e5b0b4 100644 --- a/ui-sequencer.cc +++ b/ui-sequencer.cc @@ -16,7 +16,7 @@ using namespace ebcl; namespace { -/*= VARIOUS IMGUI HELPERS ====================================================*/ +/*= VARIOUS HELPERS ==========================================================*/ bool FakeTab_( char const* const name , @@ -34,6 +34,18 @@ bool FakeTab_( return rv; } +void TimeToString_( + char* const buffer , + const size_t bSize , + const float time ) noexcept +{ + const float msf{ fmod( time , 1.f ) }; + const float sf{ fmod( time - msf , 60.f ) }; + snprintf( buffer , bSize , "%02d:%02d.%03d" , + uint32_t( ( time - msf - sf ) / 60.f ) , + uint32_t( sf ) , uint32_t( msf * 1000.f ) ); +} + /*= T_ChangeDurationDialog_ ==================================================*/ @@ -139,10 +151,62 @@ struct T_SyncViewImpl_ static constexpr float BarWidth = 40.f; static constexpr float TrackHeight = 15.f; static constexpr float TrackPadding = 2.f; + static constexpr float PointRadius = ( TrackHeight - 2.f ) * .5f; + static constexpr float PointRadiusSqr = PointRadius * PointRadius; bool display( ) noexcept; private: + // Track display data + struct T_TrackDisplay + { + T_String id; + bool isOverride; + ImRect area; + uint32_t dispSegs; + uint32_t firstSeg; + }; + struct T_TrackSegDisplay + { + uint32_t track; + uint32_t seg; + ImRect area; + uint32_t dispPoints; + uint32_t firstPoint; + }; + struct T_TrackPointDisplay + { + uint32_t seg; + uint32_t index; + ImVec2 center; + enum { + START , + MIDDLE , + END + } mode; + }; + + // Description for mouse locations in the sequencer window + enum class E_MousePosType + { + NONE , + TRACK , + SEGMENT , + POINT + }; + struct T_MousePos + { + E_MousePosType type; + uint32_t index; + }; + + // Type of sub-windows + enum E_SubWindow { + SW_NONE , + SW_CURVE_SELECTOR , + SW_OVERRIDE_SELECTOR , + }; + // Make sure all displayed curves/inputs/overrides still exist void checkSelectedCurves( ) noexcept; void displayToolbar( ) noexcept; @@ -150,18 +214,23 @@ struct T_SyncViewImpl_ void computeMetrics( float innerWidth ) noexcept; void sequencerWidget( ) noexcept; void sequencerHeader( ImRect const& bb ) noexcept; - bool sequencerBody( ImRect const& bb ) noexcept; - bool sequencerTracks( + void sequencerBody( ImRect const& bb ) noexcept; + void sequencerTracks( float& hue , ImRect& bb , ImRect const& container ) noexcept; - bool sequencerTrack( + void sequencerTrack( float& hue , ImRect const& bb , ImRect const& container , T_String const& name , T_SyncCurve const* curve ) noexcept; + void displayTooltips( + const float time ) noexcept; + + T_MousePos getMousePos( ) const noexcept; + void displayCurveSelectorWindow( ) noexcept; void displayCurveSelector( ) noexcept; void displayOverrideSelector( ) noexcept; @@ -191,19 +260,21 @@ struct T_SyncViewImpl_ float totalPixels; float startPixel; + // Track display + T_Array< T_TrackDisplay > dspTracks; + T_Array< T_TrackSegDisplay > dspSegments; + T_Array< T_TrackPointDisplay > dspPoints; + // Zoom area selection bool zoomInProgress{ false }; bool justZoomed{ false }; float firstZoomPixel; float curZoomPixel; - // Curve display / edition - enum E_SubWindow { - SW_NONE , - SW_CURVE_SELECTOR , - SW_OVERRIDE_SELECTOR , - }; + // Sub-windows E_SubWindow sub{ SW_NONE }; + + // Curve selection T_KeyValueTable< T_String , bool > sCurves; T_Set< T_String > sOverrides; T_String curveFinder; @@ -387,17 +458,13 @@ void T_SyncViewImpl_::sequencerWidget( ) noexcept sequencerHeader( bbHeader ); PopID( ); } - bool mouseConsumed{ false }; if ( bbDisplay.Min.y < bbDisplay.Max.y && ItemAdd( bbDisplay , dspId ) ) { PushID( dspId ); - mouseConsumed = sequencerBody( bbDisplay ); + sequencerBody( bbDisplay ); PopID( ); } PopID( ); - if ( mouseConsumed ) { - EndGroup( ); - return; - } + EndGroup( ); auto& io( GetIO( ) ); if ( hovered && ( io.MouseDown[ 0 ] || io.MouseDown[ 1 ] ) ) { @@ -406,46 +473,56 @@ void T_SyncViewImpl_::sequencerWidget( ) noexcept } const bool active( GetCurrentContext( )->ActiveId == seqId ); - if ( hovered && !active && io.MouseWheel != 0 ) { - zoomLevel = ImSaturate( zoomLevel + .025 * io.MouseWheel ); - } else if ( active ) { - if ( io.MouseDown[ 0 ] ) { - const float p{ io.MousePos.x - bbAll.Min.x + startPixel }; - auto& sync( Common::Sync( ) ); - sync.setTime( p * Common::Sync( ).duration( ) / totalPixels ); + auto& sync( Common::Sync( ) ); + const float mPixels{ io.MousePos.x - bbAll.Min.x + startPixel }; + const float mTime{ mPixels * sync.duration( ) / totalPixels }; + + if ( !active ) { + if ( !hovered ) { + return; } - if ( io.MouseDown[ 1 ] ) { - const float p{ io.MousePos.x - bbAll.Min.x + startPixel }; - if ( !zoomInProgress ) { - firstZoomPixel = p; - zoomInProgress = true; - } - curZoomPixel = p; - } else if ( zoomInProgress ) { - zoomInProgress = false; - justZoomed = true; - const auto zMin{ std::min( firstZoomPixel , curZoomPixel ) } , - zMax{ std::max( firstZoomPixel , curZoomPixel ) } , - diff{ zMax - zMin }; - if ( diff > 4 ) { - auto& sync( Common::Sync( ) ); - const float u( sync.durationUnits( ) ); - startPos = zMin * u / totalPixels; - if ( ( width - 2.f ) / u >= BarWidth ) { - zoomLevel = 0; - } else { - const auto length{ std::min( u , diff * u / totalPixels ) }; - const auto ppu{ std::min( ( width - 2 ) / length , BarWidth ) }; - zoomLevel = ( ppu - BarWidth ) / ( BarWidth - width / u ) + 1; - } - } + if ( io.MouseWheel != 0 ) { + zoomLevel = ImSaturate( zoomLevel + .025 * io.MouseWheel ); } - if ( !( io.MouseDown[ 0 ] || io.MouseDown[ 1 ] ) ) { - ClearActiveID( ); + if ( bbDisplay.Contains( io.MousePos ) ) { + displayTooltips( mTime ); + } + return; + } + + if ( io.MouseDown[ 0 ] ) { + sync.setTime( mTime ); + } + + if ( io.MouseDown[ 1 ] ) { + if ( !zoomInProgress ) { + firstZoomPixel = mPixels; + zoomInProgress = true; + } + curZoomPixel = mPixels; + + } else if ( zoomInProgress ) { + zoomInProgress = false; + justZoomed = true; + const auto zMin{ std::min( firstZoomPixel , curZoomPixel ) } , + zMax{ std::max( firstZoomPixel , curZoomPixel ) } , + diff{ zMax - zMin }; + if ( diff > 4 ) { + const float u( sync.durationUnits( ) ); + startPos = zMin * u / totalPixels; + if ( ( width - 2.f ) / u >= BarWidth ) { + zoomLevel = 0; + } else { + const auto length{ std::min( u , diff * u / totalPixels ) }; + const auto ppu{ std::min( ( width - 2 ) / length , BarWidth ) }; + zoomLevel = ( ppu - BarWidth ) / ( BarWidth - width / u ) + 1; + } } } - EndGroup( ); + if ( !( io.MouseDown[ 0 ] || io.MouseDown[ 1 ] ) ) { + ClearActiveID( ); + } } void T_SyncViewImpl_::computeMetrics( @@ -526,11 +603,7 @@ void T_SyncViewImpl_::sequencerHeader( const ImVec2 taEnd{ taStart + ImVec2{ barWidth , inner.Max.y - inner.Min.y } }; const float time{ bar * timePerBar }; - const float msf{ fmod( time , 1.f ) }; - const float sf{ fmod( time - msf , 60.f ) }; - snprintf( buffer , sizeof( buffer ) , "%02d:%02d.%03d" , - uint32_t( ( time - msf - sf ) / 60.f ) , - uint32_t( sf ) , uint32_t( msf * 1000.f ) ); + TimeToString_( buffer , sizeof( buffer ) , time ); RenderTextClipped( taStart , taEnd , buffer , nullptr , nullptr , ImVec2{ .5f , .05f + ( ( bar % 2 ) ? .9f : 0.f ) } , &inner ); pos += barWidth; @@ -540,7 +613,7 @@ void T_SyncViewImpl_::sequencerHeader( PopFont( ); } -bool T_SyncViewImpl_::sequencerBody( +void T_SyncViewImpl_::sequencerBody( ImRect const& bb ) noexcept { using namespace ImGui; @@ -574,7 +647,6 @@ bool T_SyncViewImpl_::sequencerBody( } // Display the curve / override controls - bool mouseConsumed{ false }; if ( sCurves.size( ) != 0 ) { ImRect subBb{ inner }; @@ -585,7 +657,7 @@ bool T_SyncViewImpl_::sequencerBody( float hue{ 0.12f }; // TODO: display overrides - mouseConsumed = sequencerTracks( hue , subBb , inner ) || mouseConsumed; + sequencerTracks( hue , subBb , inner ); } if ( cursorPos >= 0 && cursorPos <= inner.GetWidth( ) ) { @@ -594,18 +666,14 @@ bool T_SyncViewImpl_::sequencerBody( ImVec2{ inner.Min.x + cursorPos , inner.Max.y - 1 } , 0xffffffff ); } - - return mouseConsumed; } - -bool T_SyncViewImpl_::sequencerTracks( +void T_SyncViewImpl_::sequencerTracks( float& hue , ImRect& subBb , ImRect const& container ) noexcept { auto& sync{ Common::Sync( ) }; const auto nc{ sCurves.size( ) }; - bool mouseConsumed{ false }; for ( auto i = 0u ; i < nc ; i ++ ) { if ( sCurves.values( )[ i ] ) { continue; @@ -613,31 +681,38 @@ bool T_SyncViewImpl_::sequencerTracks( auto const& name{ sCurves.keys( )[ i ] }; auto* const curve{ sync.getCurve( name ) }; - mouseConsumed = sequencerTrack( hue , subBb , container , name , curve ) - || mouseConsumed; + sequencerTrack( hue , subBb , container , name , curve ); subBb.Min.y += TrackHeight + 2 * TrackPadding; subBb.Max.y += TrackHeight + 2 * TrackPadding; hue = fmodf( hue + .17f , 1.f ); } - return mouseConsumed; } -bool T_SyncViewImpl_::sequencerTrack( +void T_SyncViewImpl_::sequencerTrack( float& hue , ImRect const& bb , ImRect const& container , - T_String const& name , + T_String const& id , T_SyncCurve const* curve ) noexcept { - using namespace ImGui; + // Don't display if the track is fully hidden if ( !container.Overlaps( bb ) ) { - return false; + return; } - auto* const dl{ GetWindowDrawList( ) }; + + // Add track display record + const auto dTrackIdx{ dspTracks.size( ) }; + auto& dTrack{ dspTracks.addNew( ) }; + dTrack.id = id; + dTrack.isOverride = false; + dTrack.dispSegs = 0; + dTrack.firstSeg = dspSegments.size( ); + dTrack.area = bb; // Compute colors + using namespace ImGui; const auto bgColor{ ColorHSVAToU32( hue , .25f , 1.f , .25f ) } , - borderColor{ ColorHSVAToU32( hue , .12f , 1.f , 1.f ) }; + borderColor{ ColorHSVAToU32( hue , .88f , 1.f , 1.f ) }; const uint32_t segColors[ 2 ] = { ColorHSVAToU32( hue - .03f , .4f , 1.f , 1.f ) , ColorHSVAToU32( hue + .03f , .4f , 1.f , 1.f ) , @@ -650,6 +725,7 @@ bool T_SyncViewImpl_::sequencerTrack( ImVec2{ ImFloor( bb.Max.x ) - 1 , ImFloor( ImMin( container.Max.y , bb.Max.y ) ) } }; + auto* const dl{ GetWindowDrawList( ) }; dl->AddRectFilled( bar.Min , bar.Max , bgColor ); if ( container.Contains( bb.GetTL( ) ) ) { dl->AddLine( bar.GetTL( ) , bar.GetTR( ) , borderColor ); @@ -667,13 +743,6 @@ bool T_SyncViewImpl_::sequencerTrack( } dl->PushClipRect( bar.Min , bar.Max ); -#if 0 - // We'll need to display the right pop-up - auto& io( GetIO( ) ); - const auto mPos{ io.MousePos }; - bool mousePosUsed{ false } , mouseConsumed{ false }; -#endif - // If there's a curve, go through all segments const auto units{ Common::Sync( ).durationUnits( ) }; const auto nSeg{ curve ? curve->segments.size( ) : 0u }; @@ -699,6 +768,17 @@ bool T_SyncViewImpl_::sequencerTrack( continue; } + // Add segment to displayed list + auto dSegIdx{ dspSegments.size( ) }; + auto& dSeg{ dspSegments.addNew( ) }; + dSeg.area = segFull; + dSeg.track = dTrackIdx; + dSeg.seg = i; + dSeg.dispPoints = 0; + dSeg.firstPoint = dspPoints.size( ); + dTrack.dispSegs ++; + + // Draw segment const ImRect segBar{ ImVec2{ ImMax( bar.Min.x , segFull.Min.x ) , segFull.Min.y } , ImVec2{ ImMin( bar.Max.x , segFull.Max.x ) , segFull.Max.y } @@ -706,73 +786,147 @@ bool T_SyncViewImpl_::sequencerTrack( dl->AddRectFilled( segBar.Min , segBar.Max , color ); dl->PushClipRect( segBar.Min , segBar.Max ); + // Handle points const auto nd{ seg.durations.size( ) }; - const auto radius{ ( TrackHeight - 2.f ) * .5f }; - const auto ym{ bb.Min.y + radius + 1 }; + const auto ym{ bb.Min.y + PointRadius + 1 }; auto cDur{ 0 }; for ( auto j = 0u ; j < nd + 1 ; j ++ ) { const ImVec2 ctr{ std::roundf( xStart + cDur * totalPixels / units ) , ym }; - dl->AddCircleFilled( ctr , radius , 0x7f000000 ); // XXX color -#if 0 - if ( ImLengthSqr( mPos - ctr ) <= radius * radius ) { - if ( ! mousePosUsed ) { - BeginTooltip( ); - stringBuffer.clear( ) << name << " (input)\nIn " - << seg.type << " segment\nAt " << segDur - << " units\nValue: " << seg.values[ j ]; - Text( "%s" , stringBuffer.data( ) ); - EndTooltip( ); - mousePosUsed = true; - } - if ( !mouseConsumed && io.MouseDown[ 0 ] ) { - printf( "fuck yeah!\n" ); - mouseConsumed = true; - } - } -#endif + dl->AddCircleFilled( ctr , PointRadius , 0x7f000000 ); // XXX color cDur += j < nd ? seg.durations[ j ] : 0; + + // Add point record + auto& dPoint{ dspPoints.addNew( ) }; + dPoint.center = ctr; + dPoint.index = j; + dPoint.mode = + j == 0 ? T_TrackPointDisplay::START : ( + j == nd ? T_TrackPointDisplay::END : + T_TrackPointDisplay::MIDDLE ); + dPoint.seg = dSegIdx; + dSeg.dispPoints ++; } dl->PopClipRect( ); - -#if 0 - if ( segBar.Contains( mPos ) && !mousePosUsed ) { - BeginTooltip( ); - stringBuffer.clear( ) << name << " (input)\nIn " - << seg.type << " segment\nDuration: " - << segDur << " units (from " << ( segStart - segDur ) - << " to " << ( segStart - 1 ) - << ")\n"; - Text( "%s" , stringBuffer.data( ) ); - EndTooltip( ); - mousePosUsed = true; - } -#endif } -#if 0 - if ( bar.Contains( mPos ) && !mousePosUsed ) { - const float time{ ( io.MousePos.x - container.Min.x + startPixel ) - * units / totalPixels }; - const float msf{ fmod( time , 1.f ) }; - const float sf{ fmod( time - msf , 60.f ) }; - char buffer[ 12 ]; - snprintf( buffer , sizeof( buffer ) , "%02d:%02d.%03d" , - uint32_t( ( time - msf - sf ) / 60.f ) , - uint32_t( sf ) , uint32_t( msf * 1000.f ) ); - BeginTooltip( ); - stringBuffer.clear( ) << name << " (input)\nNot defined at " - << buffer << '\0'; - Text( "%s" , stringBuffer.data( ) ); - EndTooltip( ); - mousePosUsed = true; - } -#endif dl->PopClipRect( ); +} - return false; +/*----------------------------------------------------------------------------*/ + +void T_SyncViewImpl_::displayTooltips( + const float time ) noexcept +{ + auto mp{ getMousePos( ) }; + if ( mp.type == E_MousePosType::NONE ) { + return; + } + + // Track / segment / point from mouse info + auto const& track( [&](){ + if ( mp.type == E_MousePosType::POINT ) { + return dspTracks[ dspSegments[ + dspPoints[ mp.index ].seg ].track ]; + } + if ( mp.type == E_MousePosType::SEGMENT ) { + return dspTracks[ dspSegments[ mp.index ].track ]; + } + return dspTracks[ mp.index ]; + }() ); + auto const* const seg( [&]() -> T_TrackSegDisplay const* { + if ( mp.type == E_MousePosType::TRACK ) { + return nullptr; + } + if ( mp.type == E_MousePosType::SEGMENT ) { + return &dspSegments[ mp.index ]; + } + return &dspSegments[ dspPoints[ mp.index ].seg ]; + }() ); + auto const* const point( [&]() -> T_TrackPointDisplay const* { + if ( mp.type != E_MousePosType::POINT ) { + return nullptr; + } + return &dspPoints[ mp.index ]; + }() ); + + // Curve from track + T_SyncCurve const* const curve{ Common::Sync( ).getCurve( track.id ) }; + assert( mp.type == E_MousePosType::TRACK || curve != nullptr ); + + // Time offset + const float dTime( [&](){ + if ( point ) { + auto tDur{ .0f }; + printf( "segseg %d\n" , seg->seg ); + for ( auto i = 0u ; i <= seg->seg ; i ++ ) { + auto const& s{ curve->segments[ i ] }; + auto const nd{ i == seg->seg + ? point->index + : s.durations.size( ) }; + printf( "s %d -> nd %d\n" , i , nd ); + for ( auto j = 0u ; j < nd ; j ++ ) { + tDur += s.durations[ j ]; + } + } + return tDur * Common::Sync( ).durationUnitSize( ); + } + return time; + }() ); + char buffer[ 12 ]; + TimeToString_( buffer , sizeof( buffer ) , dTime ); + + const float value{ point ? curve->segments[ seg->seg ].values[ point->index ] : -.666f }; // FIXME + + stringBuffer.clear( ) << track.id << " (input)\n"; + if ( mp.type == E_MousePosType::TRACK ) { + stringBuffer << "No segment"; + } else { + auto const& s{ curve->segments[ seg->seg ] }; + if ( mp.type == E_MousePosType::SEGMENT ) { + stringBuffer << "Segment type: " << s.type; + } else { + stringBuffer << "On " << s.type << " segment, index " << point->index; + } + } + stringBuffer << "\nTime: " << buffer << "\nValue: " << value; + + using namespace ImGui; + stringBuffer << '\0'; + BeginTooltip( ); + Text( "%s" , stringBuffer.data( ) ); + EndTooltip( ); +} + +/*----------------------------------------------------------------------------*/ + +T_SyncViewImpl_::T_MousePos T_SyncViewImpl_::getMousePos( ) const noexcept +{ + auto const& mp{ ImGui::GetIO( ).MousePos }; + for ( auto i = 0u ; i < dspTracks.size( ) ; i ++ ) { + auto const& track{ dspTracks[ i ] }; + if ( !track.area.Contains( mp ) ) { + continue; + } + for ( auto j = 0u ; j < track.dispSegs ; j ++ ) { + auto const& seg{ dspSegments[ track.firstSeg + j ] }; + if ( !seg.area.Contains( mp ) ) { + continue; + } + for ( auto k = 0u ; k < seg.dispPoints ; k ++ ) { + auto const& p{ dspPoints[ seg.firstPoint + k ] }; + if ( ImLengthSqr( mp - p.center ) <= PointRadiusSqr ) { + return T_MousePos{ E_MousePosType::POINT , + seg.firstPoint + k }; + } + } + return T_MousePos{ E_MousePosType::SEGMENT , i }; + } + return T_MousePos{ E_MousePosType::TRACK , i }; + } + return T_MousePos{ E_MousePosType::NONE , 0 }; } /*----------------------------------------------------------------------------*/