Emmanuel BENOîT
da570c14bf
+ Buttons can be configured + Message box implementation + Added confirmation dialogs for some of the stuff that needed them.
807 lines
22 KiB
C++
807 lines
22 KiB
C++
#include "externals.hh"
|
|
#include "syncview.hh"
|
|
#include "globals.hh"
|
|
#include "window.hh"
|
|
#include "syncedit.hh"
|
|
|
|
#define IMGUI_DEFINE_MATH_OPERATORS
|
|
#include <imgui_internal.h>
|
|
|
|
using namespace ebcl;
|
|
|
|
namespace {
|
|
|
|
/*= VARIOUS IMGUI HELPERS ====================================================*/
|
|
|
|
bool FakeTab_(
|
|
char const* const name ,
|
|
const bool disabled ,
|
|
const float width = 0.f )
|
|
{
|
|
using namespace ImGui;
|
|
if ( disabled ) {
|
|
PushItemFlag( ImGuiItemFlags_Disabled , true );
|
|
PushStyleVar( ImGuiStyleVar_Alpha ,
|
|
GetStyle( ).Alpha * .5f );
|
|
}
|
|
const bool rv( Button( name , ImVec2{ width , 0.f } ) );
|
|
if ( disabled ) {
|
|
PopItemFlag( );
|
|
PopStyleVar( );
|
|
}
|
|
return rv;
|
|
}
|
|
|
|
void ToolbarSeparator_( ) noexcept
|
|
{
|
|
using namespace ImGui;
|
|
SameLine( );
|
|
VerticalSeparator( );
|
|
SameLine( );
|
|
}
|
|
|
|
bool ToolbarButton_(
|
|
char const* const string ,
|
|
ImVec2 const& size ,
|
|
char const* const tooltip = nullptr ) noexcept
|
|
{
|
|
using namespace ImGui;
|
|
const bool rv{ Button( string , size ) };
|
|
if ( tooltip && IsItemHovered( ) ) {
|
|
BeginTooltip( );
|
|
Text( tooltip );
|
|
EndTooltip( );
|
|
}
|
|
return rv;
|
|
}
|
|
|
|
|
|
/*= T_ChangeDurationDialog_ ==================================================*/
|
|
|
|
class T_ChangeDurationDialog_ : public A_ModalDialog
|
|
{
|
|
private:
|
|
const uint32_t units0_;
|
|
const float uSize0_;
|
|
const uint32_t uPerMinute0_;
|
|
uint32_t units_;
|
|
float uSize_;
|
|
uint32_t uPerMinute_;
|
|
bool scale_{ true };
|
|
|
|
protected:
|
|
uint8_t drawDialog( ) noexcept override;
|
|
bool onButton( uint8_t id ) noexcept override;
|
|
|
|
public:
|
|
T_ChangeDurationDialog_(
|
|
uint32_t units ,
|
|
float uSize ) noexcept;
|
|
};
|
|
|
|
/*----------------------------------------------------------------------------*/
|
|
|
|
T_ChangeDurationDialog_::T_ChangeDurationDialog_(
|
|
const uint32_t units ,
|
|
const float uSize ) noexcept
|
|
: A_ModalDialog{ "Set demo duration...##duration-dialog" } ,
|
|
units0_{ units } , uSize0_{ std::max( 1.f / 60.f , uSize ) } ,
|
|
uPerMinute0_{ uint32_t( ImClamp( roundf( 60.f / uSize0_ ) , 1.f , 3600.f ) ) } ,
|
|
units_{ units0_ } , uSize_{ uSize0_ } , uPerMinute_{ uPerMinute0_ }
|
|
{
|
|
setInitialSize( 300.f , 180.f );
|
|
addButton( "OK" );
|
|
addButton( "Cancel" );
|
|
}
|
|
|
|
uint8_t T_ChangeDurationDialog_::drawDialog( ) noexcept
|
|
{
|
|
using namespace ImGui;
|
|
|
|
int tUnits( units_ );
|
|
if ( DragInt( "Duration##units" , &tUnits , .1f , 1 , INT32_MAX , "%.0f unit(s)" ) ) {
|
|
units_ = uint32_t( std::min( INT32_MAX , std::max( 1 , tUnits ) ) );
|
|
}
|
|
|
|
float tDuration( units_ * uSize_ );
|
|
if ( DragFloat( "##seconds" , &tDuration , 1.f , .001f , FLT_MAX , "%.3f second(s)" ) ) {
|
|
units_ = std::min( uint32_t( INT32_MAX ) , std::max( 1u ,
|
|
uint32_t( roundf( tDuration / uSize_ ) ) ) );
|
|
}
|
|
|
|
Separator( );
|
|
|
|
int tUsize( floorf( uSize_ * 1000.f ) );
|
|
if ( SliderInt( "Units" , &tUsize , 16 , 2000 , "%.0f ms" ) ) {
|
|
const float pDur{ uSize_ * units_ };
|
|
uSize_ = std::min( 2.f , std::max( 1.f / 60.f ,
|
|
.001f * tUsize ) );
|
|
uPerMinute_ = roundf( 60.f / uSize_ );
|
|
units_ = uint32_t( roundf( pDur / uSize_ ) );
|
|
}
|
|
|
|
int tPerMin( uPerMinute_ );
|
|
if ( SliderInt( "Units/minute" , &tPerMin , 30 , 3600 ) ) {
|
|
const float pDur{ uSize_ * units_ };
|
|
uPerMinute_ = std::max( 30u , std::min( 3600u , uint32_t( tPerMin ) ) );
|
|
uSize_ = 60.f / uPerMinute_;
|
|
units_ = uint32_t( roundf( pDur / uSize_ ) );
|
|
}
|
|
|
|
if ( uPerMinute0_ == uPerMinute_ ) {
|
|
PushItemFlag( ImGuiItemFlags_Disabled , true );
|
|
PushStyleVar( ImGuiStyleVar_Alpha ,
|
|
GetStyle( ).Alpha * .5f );
|
|
}
|
|
Checkbox( "Scale curves" , &scale_ );
|
|
if ( uPerMinute0_ == uPerMinute_ ) {
|
|
PopItemFlag( );
|
|
PopStyleVar( );
|
|
}
|
|
|
|
const bool eo{ units_ != units0_ || uPerMinute_ != uPerMinute0_ };
|
|
return eo ? 3 : 2;
|
|
}
|
|
|
|
bool T_ChangeDurationDialog_::onButton(
|
|
const uint8_t button ) noexcept
|
|
{
|
|
if ( button == 0 ) {
|
|
SyncEditor::SetDuration( units_ ,
|
|
uPerMinute_ != uPerMinute0_ ? uSize_ : uSize0_ ,
|
|
scale_ );
|
|
}
|
|
return true;
|
|
}
|
|
|
|
|
|
/*= T_SyncViewImpl_ ==========================================================*/
|
|
|
|
struct T_SyncViewImpl_
|
|
{
|
|
static constexpr float SeqHeaderHeight = 24;
|
|
static constexpr float BarWidth = 40;
|
|
|
|
bool display( ) noexcept;
|
|
|
|
private:
|
|
// Make sure all displayed curves/inputs/overrides still exist
|
|
void checkSelectedCurves( ) noexcept;
|
|
void displayToolbar( ) noexcept;
|
|
|
|
void computeMetrics( float innerWidth ) noexcept;
|
|
void sequencerWidget( ) noexcept;
|
|
void sequencerHeader( ImRect const& bb ) noexcept;
|
|
void sequencerBody( ImRect const& bb ) noexcept;
|
|
void sequencerCurves( ) noexcept;
|
|
void sequencerCurve( T_SyncCurve const& curve ) noexcept;
|
|
|
|
void displayCurveSelectorWindow( ) noexcept;
|
|
void displayCurveSelector( ) noexcept;
|
|
void displayOverrideSelector( ) noexcept;
|
|
|
|
// Colors, sizes, etc.
|
|
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 } ) };
|
|
const uint32_t ColSelection{ ImGui::GetColorU32( ImVec4{ .8 , 1 , .8 , .2 } ) };
|
|
const ImVec2 BtSize{ 20 , 0 };
|
|
|
|
// Sequencer settings
|
|
float zoomLevel{ 0.f };
|
|
float startPos{ 0.f };
|
|
bool followTime{ true };
|
|
|
|
// Misc stuff
|
|
T_StringBuilder stringBuffer; // XXX damn this shit to fucking hell
|
|
|
|
// Computed metrics
|
|
float barWidth;
|
|
float cursorPos;
|
|
uint32_t startBar;
|
|
float startBarPos;
|
|
float timePerBar;
|
|
float totalPixels;
|
|
float startPixel;
|
|
|
|
// 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 ,
|
|
};
|
|
E_SubWindow sub{ SW_NONE };
|
|
T_KeyValueTable< T_String , bool > sCurves;
|
|
T_Set< T_String > sOverrides;
|
|
T_String curveFinder;
|
|
};
|
|
|
|
constexpr float T_SyncViewImpl_::BarWidth;
|
|
|
|
/*----------------------------------------------------------------------------*/
|
|
|
|
bool T_SyncViewImpl_::display( ) noexcept
|
|
{
|
|
using namespace ImGui;
|
|
auto const& dspSize( GetIO( ).DisplaySize );
|
|
|
|
// Window set-up
|
|
SetNextWindowSize( ImVec2( dspSize.x , dspSize.y * .34f ) , ImGuiSetCond_Appearing );
|
|
SetNextWindowPos( ImVec2( 0 , dspSize.y * .66f ) , ImGuiSetCond_Appearing );
|
|
bool displayed{ true };
|
|
Begin( "Sequencer" , &displayed , ImGuiWindowFlags_NoCollapse
|
|
| ImGuiWindowFlags_NoScrollWithMouse );
|
|
if ( !displayed ) {
|
|
End( );
|
|
return false;
|
|
}
|
|
|
|
checkSelectedCurves( );
|
|
displayToolbar( );
|
|
|
|
//----------------------------------------------------------------------
|
|
// Sequencer widget & subwindows
|
|
|
|
BeginChild( "##sequencer" , ImVec2{ 0 , 0 } , false ,
|
|
ImGuiWindowFlags_NoScrollWithMouse );
|
|
PushItemWidth( -1 );
|
|
sequencerWidget( );
|
|
PopItemWidth( );
|
|
EndChild( );
|
|
End( );
|
|
|
|
switch ( sub ) {
|
|
case SW_NONE:
|
|
break;
|
|
|
|
case SW_CURVE_SELECTOR:
|
|
case SW_OVERRIDE_SELECTOR:
|
|
displayCurveSelectorWindow( );
|
|
break;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void T_SyncViewImpl_::checkSelectedCurves( ) noexcept
|
|
{
|
|
auto& sync{ Globals::Sync( ) };
|
|
|
|
// Check for "dead" overrides
|
|
{
|
|
bool ovRemoved{ false };
|
|
for ( auto i = 0u ; i < sOverrides.size( ) ; ) {
|
|
if ( sync.overrideExists( sOverrides[ i ] ) ) {
|
|
i ++;
|
|
} else {
|
|
sOverrides.remove( sOverrides[ i ] );
|
|
ovRemoved = true;
|
|
}
|
|
}
|
|
if ( !ovRemoved ) {
|
|
return;
|
|
}
|
|
}
|
|
|
|
// Remove all curves that come from overrides
|
|
for ( auto i = 0u ; i < sCurves.size( ) ; ) {
|
|
if ( sCurves.values( )[ i ] ) {
|
|
sCurves.remove( sCurves.keys( )[ i ] );
|
|
} else {
|
|
i ++;
|
|
}
|
|
}
|
|
|
|
// Re-add curves for the remaining overrides
|
|
const auto no{ sOverrides.size( ) };
|
|
for ( auto i = 0u ; i < no ; i ++ ) {
|
|
auto const* od{ sync.getOverride( sOverrides[ i ] ) };
|
|
assert( od );
|
|
const auto ni{ od->inputNames( ).size( ) };
|
|
for ( auto j = 0u ; j < ni ; j ++ ) {
|
|
const bool ok{ sCurves.add( od->inputNames( )[ j ] , true ) };
|
|
assert( ok ); (void) ok;
|
|
}
|
|
}
|
|
}
|
|
|
|
void T_SyncViewImpl_::displayToolbar( ) noexcept
|
|
{
|
|
using namespace ImGui;
|
|
auto& sync( Globals::Sync( ) );
|
|
|
|
if ( ToolbarButton_( sync.playing( ) ? ICON_FA_STOP : ICON_FA_PLAY , BtSize ,
|
|
sync.playing( ) ? "Stop" : "Play" ) ) {
|
|
sync.playing( ) = !sync.playing( ) && !sync.finished( );
|
|
}
|
|
|
|
SameLine( );
|
|
|
|
if ( ToolbarButton_( ICON_FA_BACKWARD , BtSize , "Rewind to 00:00.000" ) ) {
|
|
sync.setTime( 0 );
|
|
}
|
|
|
|
ToolbarSeparator_( );
|
|
|
|
Text( ICON_FA_SEARCH );
|
|
bool zoomHovered{ IsItemHovered( ) };
|
|
SameLine( );
|
|
PushItemWidth( 100 );
|
|
SliderFloat( "##zoom" , &zoomLevel , 0 , 1 , "%.2f" );
|
|
if ( zoomHovered || IsItemHovered( ) ) {
|
|
BeginTooltip( );
|
|
Text( "Zoom level" );
|
|
EndTooltip( );
|
|
}
|
|
PopItemWidth( );
|
|
|
|
SameLine( );
|
|
|
|
if ( ToolbarButton_( followTime ? ICON_FA_LOCK : ICON_FA_UNLOCK , BtSize ,
|
|
followTime ? "Follows the current position.\nClick to untie."
|
|
: "Not tied to the current position.\nClick to follow." ) ) {
|
|
followTime = !followTime;
|
|
}
|
|
|
|
ToolbarSeparator_( );
|
|
|
|
if ( ToolbarButton_( ICON_FA_CLOCK_O , BtSize , "Change duration and time units." ) ) {
|
|
Globals::Window( ).pushDialog( NewOwned< T_ChangeDurationDialog_ >(
|
|
sync.durationUnits( ) , sync.durationUnitSize( ) ) );
|
|
}
|
|
|
|
ToolbarSeparator_( );
|
|
|
|
if ( ToolbarButton_( ICON_FA_LINE_CHART , BtSize ,
|
|
"Select curves or sets thereof to display & edit." ) ) {
|
|
const bool displaySelector{ sub == SW_CURVE_SELECTOR
|
|
|| sub == SW_OVERRIDE_SELECTOR };
|
|
sub = displaySelector ? SW_NONE : SW_CURVE_SELECTOR;
|
|
curveFinder = T_String{};
|
|
}
|
|
}
|
|
|
|
/*------------------------------------------------------------------------------*/
|
|
|
|
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 * 2 } };
|
|
const ImRect bbAll{ bbHeader.Min , bbDisplay.Max };
|
|
|
|
ItemSize( bbAll , style.FramePadding.y );
|
|
if ( !ItemAdd( bbAll , seqId ) ) {
|
|
return;
|
|
}
|
|
const bool hovered{ ItemHoverable( bbAll , seqId ) };
|
|
computeMetrics( std::max( 0.f , width - 2.f ) );
|
|
|
|
BeginGroup( );
|
|
const auto hdrId{ win->GetID( "##header" ) };
|
|
const auto dspId{ win->GetID( "##display" ) };
|
|
PushID( seqId );
|
|
if ( ItemAdd( bbHeader , hdrId ) ) {
|
|
PushID( hdrId );
|
|
sequencerHeader( bbHeader );
|
|
PopID( );
|
|
}
|
|
if ( bbDisplay.Min.y < bbDisplay.Max.y && ItemAdd( bbDisplay , dspId ) ) {
|
|
PushID( dspId );
|
|
sequencerBody( bbDisplay );
|
|
PopID( );
|
|
}
|
|
PopID( );
|
|
|
|
auto& io( GetIO( ) );
|
|
if ( hovered && ( io.MouseDown[ 0 ] || io.MouseDown[ 1 ] ) ) {
|
|
SetActiveID( seqId , win );
|
|
FocusWindow( win );
|
|
}
|
|
|
|
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( Globals::Sync( ) );
|
|
sync.setTime( p * Globals::Sync( ).duration( ) / totalPixels );
|
|
}
|
|
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( Globals::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.MouseDown[ 0 ] || io.MouseDown[ 1 ] ) ) {
|
|
ClearActiveID( );
|
|
}
|
|
}
|
|
|
|
EndGroup( );
|
|
}
|
|
|
|
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 ) };
|
|
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 ) {
|
|
if ( justZoomed ) {
|
|
followTime = false;
|
|
} else {
|
|
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 );
|
|
}
|
|
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( );
|
|
assert( startBarPos <= 0 );
|
|
assert( totalPixels >= innerWidth );
|
|
|
|
justZoomed = false;
|
|
}
|
|
|
|
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 );
|
|
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 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( );
|
|
}
|
|
|
|
void T_SyncViewImpl_::sequencerBody(
|
|
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 , ColMain );
|
|
dl->AddRect( bb.Min , bb.Max , ColFrame );
|
|
if ( zoomInProgress ) {
|
|
const float z0{ ImClamp( firstZoomPixel - startPixel + inner.Min.x , inner.Min.x , inner.Max.x ) } ,
|
|
z1{ ImClamp( curZoomPixel - startPixel + bb.Min.x , inner.Min.x , inner.Max.x ) };
|
|
const float zMin{ std::min( z0 , z1 ) } , zMax{ std::max( z0 , z1 ) };
|
|
if ( zMin != zMax ) {
|
|
dl->AddRectFilled( ImVec2{ zMin , inner.Min.y } ,
|
|
ImVec2{ zMax , inner.Max.y } ,
|
|
ColSelection );
|
|
}
|
|
}
|
|
|
|
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 ++;
|
|
}
|
|
|
|
// Display the curve / override controls
|
|
if ( sCurves.size( ) != 0 ) {
|
|
BeginGroup( );
|
|
// TODO: display overrides
|
|
sequencerCurves( );
|
|
EndGroup( );
|
|
}
|
|
|
|
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 );
|
|
}
|
|
}
|
|
|
|
void T_SyncViewImpl_::sequencerCurves( ) noexcept
|
|
{
|
|
auto& sync{ Globals::Sync( ) };
|
|
const auto nc{ sCurves.size( ) };
|
|
for ( auto i = 0u ; i < nc ; i ++ ) {
|
|
if ( sCurves.values( )[ i ] ) {
|
|
continue;
|
|
}
|
|
auto* const curve{ sync.getCurve( sCurves.keys( )[ i ] ) };
|
|
assert( curve );
|
|
sequencerCurve( *curve );
|
|
}
|
|
}
|
|
|
|
void T_SyncViewImpl_::sequencerCurve(
|
|
T_SyncCurve const& curve ) noexcept
|
|
{
|
|
#warning implement the fuck
|
|
}
|
|
|
|
/*----------------------------------------------------------------------------*/
|
|
|
|
void T_SyncViewImpl_::displayCurveSelectorWindow( ) noexcept
|
|
{
|
|
using namespace ImGui;
|
|
auto const& dspSize( GetIO( ).DisplaySize );
|
|
|
|
// Window set-up
|
|
SetNextWindowSize( ImVec2( dspSize.x * .25f , dspSize.y * .66f - 20 ) , ImGuiSetCond_Appearing );
|
|
SetNextWindowPos( ImVec2( dspSize.x * .75f , 20 ) , ImGuiSetCond_Appearing );
|
|
bool displayed{ true };
|
|
Begin( "Display curves" , &displayed , ImGuiWindowFlags_NoCollapse );
|
|
if ( !displayed ) {
|
|
End( );
|
|
sub = SW_NONE;
|
|
return;
|
|
}
|
|
|
|
// "Tabs"
|
|
const ImVec2 ws( GetWindowContentRegionMax( ) );
|
|
auto const& style( GetStyle( ) );
|
|
const float innerWidth{ ws.x - 2 * style.FramePadding.x };
|
|
constexpr float nButtons{ 2.f };
|
|
const float buttonWidth{ std::max( 50.f ,
|
|
( innerWidth - nButtons * style.FramePadding.x ) / nButtons ) };
|
|
|
|
if ( FakeTab_( "Individual inputs" , sub == SW_CURVE_SELECTOR , buttonWidth ) ) {
|
|
sub = SW_CURVE_SELECTOR;
|
|
}
|
|
SameLine( 0 );
|
|
if ( FakeTab_( "Overrides" , sub == SW_OVERRIDE_SELECTOR , buttonWidth ) ) {
|
|
sub = SW_OVERRIDE_SELECTOR;
|
|
}
|
|
|
|
// Content
|
|
switch ( sub ) {
|
|
case SW_CURVE_SELECTOR:
|
|
displayCurveSelector( );
|
|
break;
|
|
case SW_OVERRIDE_SELECTOR:
|
|
displayOverrideSelector( );
|
|
break;
|
|
default:
|
|
fprintf( stderr , "unexpected bullshit in sync view\n" );
|
|
std::abort( );
|
|
}
|
|
End( );
|
|
}
|
|
|
|
void T_SyncViewImpl_::displayCurveSelector( ) noexcept
|
|
{
|
|
using namespace ImGui;
|
|
T_Array< T_String > names{ Globals::Sync( ).inputNames( ) };
|
|
|
|
// Search box; FIXME, this is utterly hacky
|
|
stringBuffer.clear( ) << curveFinder;
|
|
while ( stringBuffer.size( ) < 100 ) {
|
|
stringBuffer << '\0';
|
|
}
|
|
Text( ICON_FA_SEARCH );
|
|
SameLine( );
|
|
PushItemWidth( -1 );
|
|
if ( InputText( "##find" , const_cast< char* >( stringBuffer.data( ) ) ,
|
|
stringBuffer.size( ) ) ) {
|
|
curveFinder = T_String{ stringBuffer.data( ) ,
|
|
uint32_t( strlen( stringBuffer.data( ) ) ) };
|
|
}
|
|
PopItemWidth( );
|
|
if ( curveFinder ) {
|
|
for ( auto i = 0u ; i < names.size( ) ; ) {
|
|
auto const& n( names[ i ] );
|
|
if ( n.find( curveFinder ) == -1 ) {
|
|
names.removeSwap( i );
|
|
} else {
|
|
i ++;
|
|
}
|
|
}
|
|
}
|
|
names.sort( );
|
|
|
|
// The list
|
|
ImGui::BeginChild( "content" );
|
|
for ( auto const& n : names ) {
|
|
const bool present{ sCurves.contains( n ) };
|
|
const bool overriden{ present && *sCurves.get( n ) };
|
|
|
|
if ( overriden ) {
|
|
PushItemFlag( ImGuiItemFlags_Disabled , true );
|
|
PushStyleVar( ImGuiStyleVar_Alpha ,
|
|
GetStyle( ).Alpha * .5f );
|
|
}
|
|
|
|
bool select{ present };
|
|
stringBuffer.clear( ) << n << '\0';
|
|
if ( Checkbox( stringBuffer.data( ) , &select ) ) {
|
|
if ( select ) {
|
|
sCurves.add( n , false );
|
|
} else {
|
|
sCurves.remove( n );
|
|
}
|
|
}
|
|
|
|
if ( overriden ) {
|
|
PopItemFlag( );
|
|
PopStyleVar( );
|
|
}
|
|
}
|
|
EndChild( );
|
|
}
|
|
|
|
void T_SyncViewImpl_::displayOverrideSelector( ) noexcept
|
|
{
|
|
using namespace ImGui;
|
|
|
|
BeginChild( "content" );
|
|
Globals::Sync( ).visitOverrides( [&]( T_SyncOverrideVisitor::T_Element element , const bool exit ) {
|
|
if ( element.hasType< T_SyncOverrideSection* >( ) ) {
|
|
auto const& sos{ *element.value< T_SyncOverrideSection* >( ) };
|
|
if ( sos.title == "*root*" ) {
|
|
return true;
|
|
}
|
|
if ( exit ) {
|
|
TreePop( );
|
|
} else {
|
|
return TreeNodeEx( &sos.cTitle[ 0 ] ,
|
|
ImGuiTreeNodeFlags_DefaultOpen );
|
|
}
|
|
} else if ( exit ) {
|
|
auto const& ov{ *element.value< A_SyncOverride* >( ) };
|
|
auto const& id{ ov.id( ) };
|
|
auto const& in{ ov.inputNames( ) };
|
|
const bool present{ sOverrides.contains( id ) };
|
|
const bool hasCurves{ !present && [&](){
|
|
for ( auto i = 0u ; i < in.size( ) ; i ++ ) {
|
|
if ( sCurves.contains( in[ i ] ) ) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}() };
|
|
|
|
if ( hasCurves ) {
|
|
PushItemFlag( ImGuiItemFlags_Disabled , true );
|
|
PushStyleVar( ImGuiStyleVar_Alpha ,
|
|
GetStyle( ).Alpha * .5f );
|
|
}
|
|
|
|
bool select{ present };
|
|
if ( Checkbox( ov.title( ) , &select ) ) {
|
|
if ( select ) {
|
|
sOverrides.add( id );
|
|
for ( auto i = 0u ; i < in.size( ) ; i ++ ) {
|
|
sCurves.add( in[ i ] , true );
|
|
}
|
|
} else {
|
|
sOverrides.remove( id );
|
|
for ( auto i = 0u ; i < in.size( ) ; i ++ ) {
|
|
sCurves.remove( in[ i ] );
|
|
}
|
|
}
|
|
}
|
|
|
|
if ( hasCurves ) {
|
|
PopItemFlag( );
|
|
PopStyleVar( );
|
|
}
|
|
}
|
|
return true;
|
|
} );
|
|
EndChild( );
|
|
}
|
|
|
|
} // namespace <anon>
|
|
|
|
|
|
/*= T_SyncView ===============================================================*/
|
|
|
|
T_SyncView::T_SyncView( ) noexcept
|
|
: A_PrivateImplementation( new T_SyncViewImpl_( ) )
|
|
{ }
|
|
|
|
bool T_SyncView::display( ) noexcept
|
|
{
|
|
return p< T_SyncViewImpl_ >( ).display( );
|
|
}
|