#include "externals.hh" #include "syncview.hh" #include "globals.hh" #include "window.hh" #define IMGUI_DEFINE_MATH_OPERATORS #include using namespace ebcl; /*= T_SyncViewImpl_ ============================================================*/ namespace { struct T_SyncViewImpl_ { static constexpr float SeqHeaderHeight = 24; static constexpr float BarWidth = 40; const uint32_t ColFrame{ ImGui::GetColorU32( ImVec4{ 0 , 0 , 0 , .8 } ) }; const uint32_t ColHeader{ ImGui::GetColorU32( ImVec4{ .5 , .5 , .5 , .8 } ) }; 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 }; bool followTime{ true }; bool display( ) noexcept; void sequencerWidget( ) noexcept; void sequencerHeader( ImRect const& bb ) noexcept; void sequencerBody( ImRect const& bb ) noexcept; }; bool T_SyncViewImpl_::display( ) noexcept { using namespace ImGui; auto const& dspSize( GetIO( ).DisplaySize ); auto& sync( Globals::Sync( ) ); // Window set-up SetNextWindowSize( ImVec2( dspSize.x , 150 ) , ImGuiSetCond_Appearing ); SetNextWindowPos( ImVec2( 0 , dspSize.y - 150 ) , ImGuiSetCond_Appearing ); bool displayed{ true }; Begin( "Sequencer" , &displayed , ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoScrollWithMouse ); if ( !displayed ) { End( ); return false; } PushItemWidth( 100 ); SliderFloat( "Zoom" , &zoomLevel , 0 , 1 , "%.2f" ); PopItemWidth( ); SameLine( ); PushID( "playing" ); if ( Button( sync.playing( ) ? "Stop" : "Play" ) ) { sync.playing( ) = !sync.playing( ) && !sync.finished( ); } PopID( ); const float d( sync.duration( ) ); float tm( sync.time( ) ); SameLine( ); PushID( "sequencer" ); PushItemWidth( -1 ); if ( SliderFloat( "" , &tm , 0 , d , "%.1fs" ) ) { sync.setTime( tm ); sync.playing( ) = sync.playing( ) && !sync.finished( ); } PopItemWidth( ); PopID( ); PushItemWidth( -1 ); sequencerWidget( ); PopItemWidth( ); End( ); return true; } void T_SyncViewImpl_::sequencerWidget( ) noexcept { using namespace ImGui; const auto width{ CalcItemWidth( ) }; auto* const win( GetCurrentWindow( ) ); const auto seqId{ win->GetID( "##sequencer" ) }; const ImVec2 cPos( win->DC.CursorPos ); const ImVec2 ws( GetWindowContentRegionMax( ) ); auto& style( ImGui::GetStyle( ) ); const ImRect bbHeader{ cPos , cPos + ImVec2( width , SeqHeaderHeight ) }; const ImRect bbDisplay{ ImVec2{ cPos.x , bbHeader.Max.y } , ImVec2{ cPos.x + width , GetWindowPos( ).y + ws.y - style.FramePadding.y } }; const ImRect bbAll{ bbHeader.Min , bbDisplay.Max }; ItemSize( bbAll , style.FramePadding.y ); 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 ); PushID( seqId ); BeginGroup( ); const auto hdrId{ win->GetID( "##header" ) }; const auto dspId{ win->GetID( "##display" ) }; if ( ItemAdd( bbHeader , hdrId ) ) { PushID( hdrId ); sequencerHeader( bbHeader ); PopID( ); } if ( bbDisplay.Min.y < bbDisplay.Max.y && ItemAdd( bbDisplay , dspId ) ) { PushID( dspId ); 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_::sequencerHeader( ImRect const& bb ) noexcept { using namespace ImGui; auto* const dl( GetWindowDrawList( ) ); 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 ); 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 ); PopStyleColor( ); PopFont( ); } void T_SyncViewImpl_::sequencerBody( ImRect const& bb ) noexcept { using namespace ImGui; auto* const dl( GetWindowDrawList( ) ); dl->AddRectFilled( bb.Min , bb.Max , ColMain ); } } // namespace /*= T_SyncView =================================================================*/ T_SyncView::T_SyncView( ) noexcept : A_PrivateImplementation( new T_SyncViewImpl_( ) ) { } bool T_SyncView::display( ) noexcept { return p< T_SyncViewImpl_ >( ).display( ); }