diff --git a/syncview.cc b/syncview.cc index b3cf1e7..7059b81 100644 --- a/syncview.cc +++ b/syncview.cc @@ -22,14 +22,22 @@ struct T_SyncViewImpl_ const uint32_t ColHeaderText{ ImGui::GetColorU32( ImVec4{ 0 , 0 , 0 , 1 } ) }; const uint32_t ColMain{ ImGui::GetColorU32( ImVec4{ .4 , .4 , .4 , .8 } ) }; - float zoomLevel{ 1.f }; - float startPos{ 100.895f }; + float zoomLevel{ 0.f }; + float startPos{ 0.f }; bool followTime{ true }; bool display( ) noexcept; + void computeMetrics( float innerWidth ) noexcept; void sequencerWidget( ) noexcept; void sequencerHeader( ImRect const& bb ) noexcept; void sequencerBody( ImRect const& bb ) noexcept; + + // Computed metrics + float barWidth; + float cursorPos; + uint32_t startBar; + float startBarPos; + float timePerBar; }; @@ -51,9 +59,21 @@ bool T_SyncViewImpl_::display( ) noexcept } PushItemWidth( 100 ); - SliderFloat( "Zoom" , &zoomLevel , 0 , 1 , "%.2f" ); + SliderFloat( "##zoom" , &zoomLevel , 0 , 1 , "%.2f" ); + if ( IsItemHovered( ) ) { + BeginTooltip( ); + Text( "Zoom level" ); + EndTooltip( ); + } PopItemWidth( ); SameLine( ); + Checkbox( "##follow" , &followTime ); + if ( IsItemHovered( ) ) { + BeginTooltip( ); + Text( "Follow cursor" ); + EndTooltip( ); + } + SameLine( ); PushID( "playing" ); if ( Button( sync.playing( ) ? "Stop" : "Play" ) ) { @@ -102,60 +122,7 @@ void T_SyncViewImpl_::sequencerWidget( ) noexcept if ( !ItemAdd( bbAll , seqId ) ) { return; } - - /* - * Scaling: - * At minimal zoom level, we want the whole "track" to fit inside the - * available width. - * At maximal zoom level, we want units from the sync manager to cover - * $MIN_UNIT_WIDTH pixels (that should depend on the text size). - * The displayed size of the units increases along with the zoom level. - * Display units are rescaled when the distance between them - * becomes >= 2 * $MIN_UNIT_WIDTH - * At worst, the sequence is only one unit long; this means that no - * scaling occurs between minimal and maximal levels. - */ - - auto& sync( Globals::Sync( ) ); - const float innerWidth{ width - 2 }; - const uint32_t units{ sync.durationUnits( ) }; - zoomLevel = ImSaturate( zoomLevel ); - const float zoom1Pixels{ std::max( units * BarWidth , innerWidth ) }; - const float totalPixels{ zoomLevel * ( zoom1Pixels - innerWidth ) + innerWidth }; - const uint32_t totalBars{ [=](){ - const float b{ std::max( std::min( totalPixels / BarWidth , float( units ) ) , 1.f ) }; - const float mod{ fmod( b , 1.f ) }; - return uint32_t( b + ( mod ? ( 1 - mod ) : 0 ) ); - }() }; - const float unitsPerBar{ float( units ) / totalBars }; - const float barWidth{ totalPixels / totalBars }; - const float absCursorPos{ sync.time( ) * totalPixels / sync.duration( ) }; - if ( followTime ) { - const float dispUnits{ innerWidth * units / totalPixels }; - const float uSize{ sync.durationUnitSize( ) }; - const float endPos{ std::min( startPos + dispUnits , float( units ) ) }; - startPos = endPos - dispUnits; - const float spp{ startPos * totalPixels / units }; - const float epp{ endPos * totalPixels / units }; - if ( absCursorPos < spp || absCursorPos > epp ) { - startPos = std::max( 0.f , sync.time( ) / uSize - unitsPerBar * .5f ); - } - } - const float unadjustedStartPixel{ totalPixels * startPos / units }; - if ( unadjustedStartPixel + innerWidth > totalPixels ) { - startPos = std::max( 0.f , totalPixels - innerWidth ); - } - const float startPixel{ totalPixels * startPos / units }; - const uint32_t startBar{ [=](){ - const float b{ startPixel * totalBars / totalPixels }; - const float mod{ fmod( b , 1.f ) }; - return uint32_t( std::max( 0 , int32_t( b - ( mod ? mod : 1 ) ) ) ); - }() }; - const float startBarPos{ startBar * barWidth - startPixel }; - const float cursorPos{ absCursorPos - startPixel }; - printf( "Z: %f; P@zl1: %f; Pt: %f; Bt: %d; U/B: %f; Bw: %f; Sp: %f; Sb: %d, SbP: %f\n" , zoomLevel , zoom1Pixels , totalPixels , totalBars , unitsPerBar , barWidth , startPixel , startBar , startBarPos ); - assert( startBarPos <= 0 ); - assert( totalPixels >= innerWidth ); + computeMetrics( std::max( 0.f , width - 2.f ) ); PushID( seqId ); BeginGroup( ); @@ -173,32 +140,94 @@ void T_SyncViewImpl_::sequencerWidget( ) noexcept sequencerBody( bbDisplay ); PopID( ); } - if ( cursorPos >= 0 && cursorPos <= width ) { - auto* const dl( GetWindowDrawList( ) ); - dl->AddLine( bbAll.Min + ImVec2{ cursorPos + 1 , 0 } , - ImVec2{ bbAll.Min.x + cursorPos + 1 , bbAll.Max.y } , - 0xffffffff ); - } EndGroup( ); PopID( ); } +void T_SyncViewImpl_::computeMetrics( + const float innerWidth ) noexcept +{ + auto& sync( Globals::Sync( ) ); + const uint32_t units{ sync.durationUnits( ) }; + zoomLevel = ImSaturate( zoomLevel ); + const float zoom1Pixels{ std::max( units * BarWidth , innerWidth ) }; + const float totalPixels{ zoomLevel * ( zoom1Pixels - innerWidth ) + innerWidth }; + const uint32_t totalBars{ [=](){ + const float b{ std::max( std::min( totalPixels / BarWidth , float( units ) ) , 1.f ) }; + const float mod{ fmod( b , 1.f ) }; + return uint32_t( b + ( mod ? ( 1 - mod ) : 0 ) ); + }() }; + const float unitsPerBar{ float( units ) / totalBars }; + barWidth = totalPixels / totalBars; + const float absCursorPos{ sync.time( ) * totalPixels / sync.duration( ) }; + if ( followTime ) { + const float dispUnits{ innerWidth * units / totalPixels }; + const float uSize{ sync.durationUnitSize( ) }; + const float endPos{ std::min( startPos + dispUnits , float( units ) ) }; + startPos = endPos - dispUnits; + const float spp{ startPos * totalPixels / units }; + const float epp{ endPos * totalPixels / units }; + if ( absCursorPos < spp || absCursorPos > epp ) { + startPos = std::max( 0.f , sync.time( ) / uSize - unitsPerBar * .5f ); + } + } + const float unadjustedStartPixel{ totalPixels * startPos / units }; + if ( unadjustedStartPixel + innerWidth > totalPixels ) { + startPos = std::max( 0.f , totalPixels - innerWidth ); + } + const float startPixel{ totalPixels * startPos / units }; + startBar = [=](){ + const float b{ startPixel * totalBars / totalPixels }; + const float mod{ fmod( b , 1.f ) }; + return uint32_t( std::max( 0 , int32_t( b - ( mod ? mod : 1 ) ) ) ); + }(); + startBarPos = startBar * barWidth - startPixel; + cursorPos = absCursorPos - startPixel; + timePerBar = unitsPerBar * sync.durationUnitSize( ); + printf( "Z: %f; P@zl1: %f; Pt: %f; Bt: %d; U/B: %f; Bw: %f; Sp: %f; Sb: %d, SbP: %f\n" , zoomLevel , zoom1Pixels , totalPixels , totalBars , unitsPerBar , barWidth , startPixel , startBar , startBarPos ); + assert( startBarPos <= 0 ); + assert( totalPixels >= innerWidth ); +} + void T_SyncViewImpl_::sequencerHeader( ImRect const& bb ) noexcept { using namespace ImGui; auto* const dl( GetWindowDrawList( ) ); + const ImRect inner{ bb.Min + ImVec2{ 1 , 1 } , bb.Max - ImVec2{ 1 , 1 } }; + + dl->AddRectFilled( inner.Min , inner.Max , ColHeader ); + dl->AddRect( bb.Min , bb.Max , ColFrame ); + + if ( cursorPos >= 0 && cursorPos <= inner.GetWidth( ) ) { + auto* const dl( GetWindowDrawList( ) ); + dl->AddLine( inner.Min + ImVec2{ cursorPos , 0 } , + ImVec2{ inner.Min.x + cursorPos , inner.Max.y - 1 } , + GetColorU32( ImVec4{ 1 , 1 , 1 , .5 } ) ); + } PushFont( Globals::Window( ).smallFont( ) ); PushStyleColor( ImGuiCol_Text , ColHeaderText ); - dl->AddRectFilled( bb.Min + ImVec2( 1 , 1 ) , bb.Max - ImVec2( 1 , 1 ) , - ColHeader ); - dl->AddRect( bb.Min , bb.Max , ColFrame ); + auto pos{ startBarPos }; + auto bar{ startBar }; + const auto max{ bb.GetWidth( ) + barWidth - 2.f }; + char buffer[ 12 ]; + while ( pos < max ) { + const ImVec2 taStart{ inner.Min + ImVec2{ pos - barWidth * .5f , 0 } }; + const ImVec2 taEnd{ taStart + ImVec2{ barWidth , inner.Max.y - inner.Min.y } }; - const ImRect textBb{ bb.Min - ImVec2{ 10 , 0 } , bb.Max + ImVec2{ 10 , 0 } }; - RenderTextClipped( textBb.Min , textBb.Max , "test!" , nullptr , nullptr , ImVec2{ 0 , .25 } , &bb ); - RenderTextClipped( textBb.Min , textBb.Max , "...icle" , nullptr , nullptr , ImVec2{ 0 , .75 } , &bb ); + 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 ) ); + RenderTextClipped( taStart , taEnd , buffer , nullptr , nullptr , + ImVec2{ .5f , .2f + ( ( bar % 2 ) ? .6f : 0.f ) } , &inner ); + pos += barWidth; + bar ++; + } PopStyleColor( ); PopFont( ); } @@ -208,8 +237,30 @@ void T_SyncViewImpl_::sequencerBody( { using namespace ImGui; auto* const dl( GetWindowDrawList( ) ); + const ImRect inner{ bb.Min + ImVec2{ 1 , 1 } , bb.Max - ImVec2{ 1 , 1 } }; - dl->AddRectFilled( bb.Min , bb.Max , ColMain ); + dl->AddRectFilled( inner.Min , inner.Max , ColMain ); + dl->AddRect( bb.Min , bb.Max , ColFrame ); + + auto pos{ startBarPos }; + auto bar{ startBar }; + const auto max{ bb.GetWidth( ) + barWidth - 2.f }; + while ( pos < max ) { + if ( pos >= 0 && pos < inner.GetWidth( ) ) { + dl->AddLine( bb.Min + ImVec2{ pos , 0 } , + ImVec2{ inner.Min.x + pos , inner.Max.y } , + 0xff000000 ); + } + pos += barWidth; + bar ++; + } + + if ( cursorPos >= 0 && cursorPos <= inner.GetWidth( ) ) { + auto* const dl( GetWindowDrawList( ) ); + dl->AddLine( inner.Min + ImVec2{ cursorPos , -1 } , + ImVec2{ inner.Min.x + cursorPos , inner.Max.y - 1 } , + 0xffffffff ); + } }