2350 lines
63 KiB
C++
2350 lines
63 KiB
C++
#include "externals.hh"
|
|
|
|
#include "common.hh"
|
|
#include "c-sync.hh"
|
|
#include "c-syncedit.hh"
|
|
|
|
#include "ui.hh"
|
|
#include "ui-app.hh"
|
|
#include "ui-overrides.hh"
|
|
#include "ui-sequencer.hh"
|
|
#include "ui-sync.hh"
|
|
#include "ui-utilities.hh"
|
|
|
|
#include "ui-imgui-sdl.hh"
|
|
|
|
#define IMGUI_DEFINE_MATH_OPERATORS
|
|
#include <imgui_internal.h>
|
|
|
|
using namespace ebcl;
|
|
|
|
namespace {
|
|
|
|
/*= VARIOUS HELPERS ==========================================================*/
|
|
|
|
bool FakeTab_(
|
|
char const* const name ,
|
|
const bool disabled ,
|
|
const float width = 0.f )
|
|
{
|
|
using namespace ImGui;
|
|
if ( disabled ) {
|
|
PushDisabled( );
|
|
}
|
|
const bool rv( Button( name , ImVec2{ width , 0.f } ) );
|
|
if ( disabled ) {
|
|
PopDisabled( );
|
|
}
|
|
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_ ==================================================*/
|
|
|
|
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_ ) {
|
|
PushDisabled( );
|
|
}
|
|
Checkbox( "Scale curves" , &scale_ );
|
|
if ( uPerMinute0_ == uPerMinute_ ) {
|
|
PopDisabled( );
|
|
}
|
|
|
|
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_PointData_ =============================================================*/
|
|
|
|
using T_TempCurveStorage_ = T_AutoArray< T_SyncCurve , 16 >;
|
|
|
|
class T_PointData_ : public sov::A_SyncData
|
|
{
|
|
private:
|
|
T_Optional< T_SyncTrackId > const& selId_;
|
|
T_Optional< uint32_t > const& selSegment_;
|
|
T_Optional< uint32_t > const& selPoint_;
|
|
T_TempCurveStorage_& cOriginals_;
|
|
T_TempCurveStorage_& cCopies_;
|
|
|
|
T_String useId_;
|
|
uint32_t useSegment_;
|
|
uint32_t usePoint_;
|
|
|
|
A_SyncOverride* ovr_;
|
|
|
|
public:
|
|
T_PointData_( T_Optional< T_SyncTrackId > const& selId ,
|
|
T_Optional< uint32_t > const& selSegment ,
|
|
T_Optional< uint32_t > const& selPoint ,
|
|
T_TempCurveStorage_& cOriginals ,
|
|
T_TempCurveStorage_& cCopies ) noexcept
|
|
: selId_( selId ) , selSegment_( selSegment ) ,
|
|
selPoint_( selPoint ) , cOriginals_( cOriginals ) ,
|
|
cCopies_( cCopies )
|
|
{ }
|
|
|
|
void use( ) noexcept;
|
|
|
|
float operator[]( uint32_t index ) const noexcept override;
|
|
bool set( uint32_t index , float value ) noexcept override;
|
|
|
|
T_OwnPtr< sov::A_SyncData > clone( ) const noexcept override;
|
|
};
|
|
|
|
/*----------------------------------------------------------------------------*/
|
|
|
|
void T_PointData_::use( ) noexcept
|
|
{
|
|
assert( selId_ && selSegment_ && selPoint_ );
|
|
assert( selId_->isOverride );
|
|
|
|
useId_ = selId_->id;
|
|
useSegment_ = *selSegment_;
|
|
usePoint_ = *selPoint_;
|
|
ovr_ = Common::Sync( ).getOverride( useId_ );
|
|
}
|
|
|
|
float T_PointData_::operator[](
|
|
const uint32_t index ) const noexcept
|
|
{
|
|
if ( !( selId_ && selSegment_ && selPoint_ && selId_->isOverride )
|
|
|| useId_ != selId_->id || useSegment_ != *selSegment_
|
|
|| usePoint_ != *selPoint_ ) {
|
|
return 0.f;
|
|
}
|
|
|
|
auto const& iNames{ ovr_->inputNames( ) };
|
|
if ( cCopies_.size( ) != iNames.size( )
|
|
|| cCopies_[ 0 ].name != iNames[ 0 ] ) {
|
|
auto const& curve{ *Common::Sync( ).getCurve( iNames[ index ] ) };
|
|
return curve.segments[ useSegment_ ].values[ usePoint_ ];
|
|
}
|
|
return cCopies_[ index ].segments[ useSegment_ ].values[ usePoint_ ];
|
|
}
|
|
|
|
bool T_PointData_::set(
|
|
const uint32_t index ,
|
|
const float value ) noexcept
|
|
{
|
|
if ( !( selId_ && selSegment_ && selPoint_ && selId_->isOverride )
|
|
|| useId_ != selId_->id || useSegment_ != *selSegment_
|
|
|| usePoint_ != *selPoint_ ) {
|
|
return false;
|
|
}
|
|
|
|
auto const& iNames{ ovr_->inputNames( ) };
|
|
const auto nn{ iNames.size( ) };
|
|
auto& sync{ Common::Sync( ) };
|
|
if ( cCopies_.size( ) != nn || cCopies_[ 0 ].name != iNames[ 0 ] ) {
|
|
cOriginals_.clear( );
|
|
cCopies_.clear( );
|
|
for ( auto i = 0u ; i < nn ; i ++ ) {
|
|
auto const* const c{ sync.getCurve( iNames[ i ] ) };
|
|
cOriginals_.add( *c );
|
|
cCopies_.add( *c );
|
|
}
|
|
}
|
|
|
|
cCopies_[ index ].segments[ useSegment_ ].values[ usePoint_ ] = value;
|
|
sync.setCurve( cCopies_[ index ] );
|
|
return true;
|
|
}
|
|
|
|
T_OwnPtr< sov::A_SyncData > T_PointData_::clone( ) const noexcept
|
|
{
|
|
auto ptr{ NewOwned< T_PointData_ >( selId_ , selSegment_ ,
|
|
selPoint_ , cOriginals_ , cCopies_ ) };
|
|
ptr->useId_ = useId_;
|
|
ptr->useSegment_ = useSegment_;
|
|
ptr->usePoint_ = usePoint_;
|
|
return ptr;
|
|
}
|
|
|
|
|
|
/*= T_SyncViewImpl_ ==========================================================*/
|
|
|
|
struct T_SyncViewImpl_
|
|
{
|
|
static constexpr float SeqHeaderHeight = 24.f;
|
|
static constexpr float BarWidth = 40.f;
|
|
static constexpr float TrackHeight = 15.f;
|
|
static constexpr float TrackPadding = 2.f;
|
|
static constexpr float TrackLabelHeight = 15.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_SyncTrackId id;
|
|
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_INPUT_SELECTOR ,
|
|
SW_OVERRIDE_SELECTOR ,
|
|
SW_TRACK ,
|
|
SW_SEGMENT ,
|
|
SW_POINT ,
|
|
};
|
|
|
|
// Type of change in progress
|
|
enum class E_ChangeType {
|
|
NONE ,
|
|
POINT_DND ,
|
|
POINT_VALUE ,
|
|
};
|
|
|
|
// Type of drag-and-drop operation in progress
|
|
enum class E_DNDType {
|
|
NONE , // No drag'n'drop
|
|
CLICK , // Clicked in an area that will not
|
|
// cause drag'n'drop to happen
|
|
TIME , // Time being adjusted
|
|
ZOOM , // Zoom region being selected
|
|
POINT , // Point being moved
|
|
};
|
|
|
|
// Make sure all displayed inputs/overrides still exist
|
|
void checkTracks( ) noexcept;
|
|
// Make sure the currently selected track/segment/point is still valid
|
|
void checkSelection( ) noexcept;
|
|
// Display the toolbar
|
|
void displayToolbar( ) noexcept;
|
|
|
|
//----------------------------------------------------------------------
|
|
// Sequencer widget methods
|
|
|
|
void sequencerWidget( ) noexcept;
|
|
void computeMetrics( float innerWidth ) noexcept;
|
|
void sequencerHeader( ImRect const& bb ) noexcept;
|
|
void sequencerBody( ImRect const& bb ) noexcept;
|
|
void sequencerTracks(
|
|
ImRect const& container ) noexcept;
|
|
void sequencerTrack(
|
|
float& hue ,
|
|
ImRect const& bb ,
|
|
ImRect const& container ,
|
|
T_SyncTrackId const& id ,
|
|
T_SyncCurve const* curve ) noexcept;
|
|
|
|
void displayTooltips(
|
|
const float time ) noexcept;
|
|
bool handlePointDrag(
|
|
const float mPixels ,
|
|
bool mouseDown ,
|
|
bool shiftPressed ) noexcept;
|
|
|
|
T_MousePos getMousePos( ) const noexcept;
|
|
|
|
//----------------------------------------------------------------------
|
|
|
|
// Track selector
|
|
void displayTrackSelectorWindow( ) noexcept;
|
|
void displayInputSelector( ) noexcept;
|
|
void displayOverrideSelector( ) noexcept;
|
|
|
|
void clearSelectedTracks( ) noexcept;
|
|
void autoselectTracks( ) noexcept;
|
|
|
|
// Helpers for selecting overrides
|
|
static bool areOverrideInputsConsistent(
|
|
A_SyncOverride const& ov ) noexcept;
|
|
static bool wouldInputsNeedAdjustment(
|
|
A_SyncOverride const& ov ) noexcept;
|
|
bool areOverrideInputsDisplayed(
|
|
A_SyncOverride const& ov ) const noexcept;
|
|
void overrideTrackToggled(
|
|
A_SyncOverride const& ov ,
|
|
bool selected ) noexcept;
|
|
|
|
// Selection display/edition windows
|
|
void displayTrackWindow( ) noexcept;
|
|
void displaySegmentWindow( ) noexcept;
|
|
void displayPointWindow( ) 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 uint32_t ColPointNormal{ ImGui::GetColorU32( ImVec4{ 0 , 0 , 0 , .25 } ) };
|
|
const uint32_t ColPointHovered{ ImGui::GetColorU32( ImVec4{ 1 , 1 , 1 , .75 } ) };
|
|
const uint32_t ColPointSelected{ ImGui::GetColorU32( ImVec4{ 0 , 0 , 0 , .75 } ) };
|
|
const ImVec2 BtSize{ 20 , 0 };
|
|
|
|
// Sequencer settings
|
|
float zoomLevel{ 0.f };
|
|
float startPos{ 0.f };
|
|
bool followTime{ true };
|
|
bool showLabels{ true };
|
|
|
|
// Misc stuff
|
|
T_StringBuilder stringBuffer; // XXX damn this shit to fucking hell
|
|
E_DNDType dnd{ E_DNDType::NONE };
|
|
|
|
// Computed metrics
|
|
bool checkLockMode{ false };
|
|
float vScroll{ 0 };
|
|
float barWidth;
|
|
float cursorPos;
|
|
uint32_t startBar;
|
|
float startBarPos;
|
|
float timePerBar;
|
|
float totalPixels;
|
|
float startPixel;
|
|
|
|
// Track display
|
|
T_Array< T_TrackDisplay > dspTracks;
|
|
T_Array< T_TrackSegDisplay > dspSegments;
|
|
T_Array< T_TrackPointDisplay > dspPoints;
|
|
|
|
// Zoom area selection
|
|
float firstZoomPixel;
|
|
float curZoomPixel;
|
|
|
|
// Selected item
|
|
T_Optional< T_SyncTrackId > selId{ };
|
|
T_Optional< uint32_t > selSegment;
|
|
T_Optional< uint32_t > selPoint;
|
|
float selPointDnDStart , selPointDnDCur;
|
|
|
|
// Original and copy of curve being modified
|
|
E_ChangeType selUpdate{ E_ChangeType::NONE };
|
|
T_TempCurveStorage_ selUpdatingOriginals;
|
|
T_TempCurveStorage_ selUpdatingCopies;
|
|
|
|
// Override edition and copypasta
|
|
T_PointData_ selEditor{ selId , selSegment , selPoint ,
|
|
selUpdatingOriginals , selUpdatingCopies };
|
|
T_String cpType;
|
|
T_AutoArray< float , 16 > cpData;
|
|
|
|
// Sub-windows
|
|
E_SubWindow sub{ SW_NONE };
|
|
E_SubWindow tsSelectedTab{ SW_OVERRIDE_SELECTOR };
|
|
|
|
// Track selection
|
|
T_KeyValueTable< T_String , bool > sInputs;
|
|
T_Set< T_SyncTrackId > sTracks;
|
|
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;
|
|
}
|
|
|
|
checkTracks( );
|
|
checkSelection( );
|
|
displayToolbar( );
|
|
|
|
// Sequencer widget
|
|
PushItemWidth( -1 );
|
|
sequencerWidget( );
|
|
PopItemWidth( );
|
|
End( );
|
|
|
|
// Close sub-window if e.g. selection has changed
|
|
switch( sub ) {
|
|
case SW_POINT:
|
|
if ( !selPoint ) {
|
|
sub = SW_SEGMENT;
|
|
}
|
|
// fallthrough
|
|
case SW_SEGMENT:
|
|
if ( !selSegment ) {
|
|
sub = SW_TRACK;
|
|
}
|
|
// fallthrough
|
|
case SW_TRACK:
|
|
if ( !selId ) {
|
|
sub = SW_NONE;
|
|
}
|
|
break;
|
|
|
|
default: break;
|
|
}
|
|
|
|
// Display selected sub-window
|
|
switch ( sub ) {
|
|
case SW_NONE:
|
|
break;
|
|
|
|
case SW_INPUT_SELECTOR:
|
|
case SW_OVERRIDE_SELECTOR:
|
|
displayTrackSelectorWindow( );
|
|
break;
|
|
|
|
case SW_TRACK:
|
|
displayTrackWindow( );
|
|
break;
|
|
|
|
case SW_SEGMENT:
|
|
displaySegmentWindow( );
|
|
break;
|
|
|
|
case SW_POINT:
|
|
displayPointWindow( );
|
|
break;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void T_SyncViewImpl_::checkTracks( ) noexcept
|
|
{
|
|
auto& sync{ Common::Sync( ) };
|
|
|
|
// Check for overrides that have gone missing or that have become
|
|
// inconsistent.
|
|
{
|
|
bool ovRemoved{ false };
|
|
for ( auto i = 0u ; i < sTracks.size( ) ; ) {
|
|
const auto& t{ sTracks[ i ] };
|
|
if ( !t.isOverride ) {
|
|
i ++;
|
|
continue;
|
|
}
|
|
|
|
A_SyncOverride const* const ovr{ sync.getOverride( t.id ) };
|
|
if ( ovr && areOverrideInputsConsistent( *ovr ) ) {
|
|
i++;
|
|
continue;
|
|
}
|
|
|
|
sTracks.remove( t );
|
|
ovRemoved = true;
|
|
}
|
|
if ( !ovRemoved ) {
|
|
return;
|
|
}
|
|
}
|
|
|
|
// Remove all curves that come from overrides
|
|
for ( auto i = 0u ; i < sInputs.size( ) ; ) {
|
|
if ( sInputs.values( )[ i ] ) {
|
|
sInputs.remove( sInputs.keys( )[ i ] );
|
|
} else {
|
|
i ++;
|
|
}
|
|
}
|
|
|
|
// Re-add curves for the remaining overrides
|
|
const auto no{ sTracks.size( ) };
|
|
for ( auto i = 0u ; i < no ; i ++ ) {
|
|
auto const& t{ sTracks[ i ] };
|
|
auto const* const od{ sync.getOverride( t.id ) };
|
|
assert( od );
|
|
const auto ni{ od->inputNames( ).size( ) };
|
|
for ( auto j = 0u ; j < ni ; j ++ ) {
|
|
const bool ok{ sInputs.add( od->inputNames( )[ j ] , true ) };
|
|
assert( ok ); (void) ok;
|
|
}
|
|
}
|
|
}
|
|
|
|
void T_SyncViewImpl_::checkSelection( ) noexcept
|
|
{
|
|
if ( !selId ) {
|
|
return;
|
|
}
|
|
auto& sync{ Common::Sync( ) };
|
|
auto const* const ovr{ selId->isOverride ? sync.getOverride( selId->id ) : nullptr };
|
|
auto const* const curve{ sync.getCurve( ovr ? ovr->inputNames( )[ 0 ] : selId->id ) };
|
|
|
|
// Missing curve
|
|
if ( !curve ) {
|
|
// Remove segment/point selection
|
|
if ( selSegment ) {
|
|
selSegment.clear( );
|
|
selPoint.clear( );
|
|
}
|
|
// If there's no matching input, unselect the track
|
|
if ( !( ovr || sync.hasInput( selId->id ) ) ) {
|
|
selId.clear( );
|
|
}
|
|
} else {
|
|
// No segment selected? We're ok.
|
|
if ( !selSegment ) {
|
|
return;
|
|
}
|
|
|
|
// Check segment and point
|
|
if ( *selSegment >= curve->segments.size( ) ) {
|
|
selSegment.clear( );
|
|
selPoint.clear( );
|
|
} else if ( selPoint && *selPoint >= curve->segments[
|
|
*selSegment ].values.size( ) ) {
|
|
selPoint.clear( );
|
|
} else {
|
|
return;
|
|
}
|
|
}
|
|
// If we were doing something with the curve, get rid of that too
|
|
if ( selUpdate != E_ChangeType::NONE ) {
|
|
selUpdate = E_ChangeType::NONE;
|
|
selUpdatingCopies.clear( );
|
|
selUpdatingOriginals.clear( );
|
|
}
|
|
}
|
|
|
|
void T_SyncViewImpl_::displayToolbar( ) noexcept
|
|
{
|
|
using namespace ImGui;
|
|
auto& sync{ Common::Sync( ) };
|
|
|
|
UI::Main( ).actionButton( sync.playing( ) ? "Stop" : "Play" );
|
|
SameLine( );
|
|
if ( ToolbarButton( ICON_FA_BACKWARD , BtSize , "Rewind to 00:00.000" ) ) {
|
|
sync.setTime( 0 );
|
|
}
|
|
SameLine( );
|
|
{
|
|
char tBuffer[ 12 ];
|
|
TimeToString_( tBuffer , sizeof( tBuffer ) , sync.time( ) );
|
|
Text( "%s" , tBuffer );
|
|
}
|
|
SameLine( );
|
|
if ( ToolbarButton( ICON_FA_CLOCK_O , BtSize , "Change duration and time units." ) ) {
|
|
UI::Main( ).pushDialog( NewOwned< T_ChangeDurationDialog_ >(
|
|
sync.durationUnits( ) , sync.durationUnitSize( ) ) );
|
|
}
|
|
|
|
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." ,
|
|
true , followTime ) ) {
|
|
followTime = !followTime;
|
|
}
|
|
|
|
ToolbarSeparator( );
|
|
|
|
if ( ToolbarButton( ICON_FA_TAGS , BtSize , "Toggle track labels." , true , showLabels ) ) {
|
|
showLabels = !showLabels;
|
|
}
|
|
SameLine( );
|
|
if ( ToolbarButton( ICON_FA_LIST , BtSize ,
|
|
"Select inputs or sets thereof to display & edit." ) ) {
|
|
const bool displaySelector{ sub == SW_INPUT_SELECTOR
|
|
|| sub == SW_OVERRIDE_SELECTOR };
|
|
sub = displaySelector ? SW_NONE : tsSelectedTab;
|
|
curveFinder = T_String{};
|
|
}
|
|
SameLine( );
|
|
if ( ToolbarButton( ICON_FA_BAN , BtSize , "Clear selected tracks." ,
|
|
sTracks.size( ) != 0 ) ) {
|
|
clearSelectedTracks( );
|
|
}
|
|
SameLine( );
|
|
if ( ToolbarButton( ICON_FA_COG , BtSize , "Select tracks automatically." ) ) {
|
|
autoselectTracks( );
|
|
}
|
|
|
|
ToolbarSeparator( );
|
|
if ( ToolbarButton( ICON_FA_MINUS , BtSize , "Show track info." ,
|
|
selId && sub != SW_TRACK ) ) {
|
|
sub = SW_TRACK;
|
|
}
|
|
SameLine( );
|
|
if ( ToolbarButton( ICON_FA_ARROWS_H , BtSize , "Show segment info." ,
|
|
selId && selSegment && sub != SW_SEGMENT ) ) {
|
|
sub = SW_SEGMENT;
|
|
}
|
|
SameLine( );
|
|
if ( ToolbarButton( ICON_FA_CIRCLE , BtSize , "Show point info." ,
|
|
selId && selSegment && selPoint && sub != SW_POINT ) ) {
|
|
sub = SW_POINT;
|
|
}
|
|
}
|
|
|
|
/*------------------------------------------------------------------------------*/
|
|
|
|
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 float widgetWidth{ width - style.ScrollbarSize };
|
|
const ImRect bbHeader{
|
|
cPos ,
|
|
cPos + ImVec2( widgetWidth , SeqHeaderHeight )
|
|
};
|
|
const ImRect bbDisplay{
|
|
ImVec2{ cPos.x , bbHeader.Max.y } ,
|
|
ImVec2{ cPos.x + widgetWidth ,
|
|
GetWindowPos( ).y + ws.y
|
|
- style.FramePadding.y * 2
|
|
- style.ScrollbarSize }
|
|
};
|
|
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 , widgetWidth - 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( );
|
|
}
|
|
|
|
// Vertical scrollbar - tracks
|
|
const float thMul{ ( showLabels ? ( TrackLabelHeight + TrackPadding ) : 0 )
|
|
+ TrackHeight + TrackPadding * 2.f };
|
|
const float totalHeight{ sTracks.size( ) * thMul + TrackPadding };
|
|
if ( vScroll > totalHeight - bbDisplay.GetHeight( ) ) {
|
|
vScroll = ImMax( 0.f , totalHeight - bbDisplay.GetHeight( ) );
|
|
}
|
|
UserScrollbar( false , totalHeight , bbDisplay.GetHeight( ) , &vScroll ,
|
|
bbHeader.GetTR( ) , bbAll.GetHeight( ) );
|
|
|
|
// Horizontal scrollbar - time
|
|
auto& sync( Common::Sync( ) );
|
|
float rsPos = startPixel;
|
|
if ( UserScrollbar( true , totalPixels , widgetWidth , &rsPos ,
|
|
bbDisplay.GetBL( ) , bbDisplay.GetWidth( ) ) ) {
|
|
startPos = sync.durationUnits( ) * rsPos / totalPixels;
|
|
checkLockMode = true;
|
|
}
|
|
|
|
PopID( );
|
|
EndGroup( );
|
|
|
|
auto& io( GetIO( ) );
|
|
if ( hovered && ( io.MouseDown[ 0 ] || io.MouseDown[ 1 ] ) ) {
|
|
SetActiveID( seqId , win );
|
|
FocusWindow( win );
|
|
}
|
|
|
|
const bool active( GetCurrentContext( )->ActiveId == seqId );
|
|
const float mPixels{ io.MousePos.x - bbAll.Min.x + startPixel };
|
|
const float mTime{ mPixels * sync.duration( ) / totalPixels };
|
|
|
|
if ( !active ) {
|
|
if ( !hovered ) {
|
|
return;
|
|
}
|
|
if ( io.MouseWheel != 0 ) {
|
|
if ( io.KeyShift ) {
|
|
zoomLevel = ImSaturate( zoomLevel + .025 * io.MouseWheel );
|
|
} else {
|
|
vScroll = ImClamp( vScroll - 3.f * io.MouseWheel ,
|
|
0.f ,
|
|
ImMax( 0.f , totalHeight - bbDisplay.GetHeight( ) ) );
|
|
}
|
|
}
|
|
if ( bbDisplay.Contains( io.MousePos ) ) {
|
|
displayTooltips( mTime );
|
|
}
|
|
if ( ImGuiIO_MouseHorizWheel != 0 ) {
|
|
startPos = ImClamp(
|
|
sync.durationUnits( )
|
|
* ( startPixel - 20.f * ImGuiIO_MouseHorizWheel )
|
|
/ totalPixels ,
|
|
0.f , sync.durationUnits( ) );
|
|
checkLockMode = true;
|
|
}
|
|
return;
|
|
}
|
|
|
|
if ( dnd == E_DNDType::ZOOM ) {
|
|
if ( io.MouseDown[ 1 ] ) {
|
|
curZoomPixel = mPixels;
|
|
return;
|
|
}
|
|
checkLockMode = 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 = ImClamp( zMin * u / totalPixels , 0.f , u );
|
|
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;
|
|
}
|
|
}
|
|
} else if ( dnd == E_DNDType::POINT
|
|
&& handlePointDrag( mPixels , io.MouseDown[ 0 ] , io.KeyShift ) ) {
|
|
return;
|
|
} else if ( dnd == E_DNDType::CLICK && ( io.MouseDown[ 0 ] || io.MouseDown[ 1 ] ) ) {
|
|
return;
|
|
} else if ( dnd == E_DNDType::TIME && io.MouseDown[ 0 ] ) {
|
|
sync.setTime( mTime );
|
|
return;
|
|
}
|
|
|
|
if ( dnd != E_DNDType::NONE && !( io.MouseDown[ 0 ] || io.MouseDown[ 1 ] ) ) {
|
|
dnd = E_DNDType::NONE;
|
|
ClearActiveID( );
|
|
return;
|
|
}
|
|
|
|
const auto mp{ getMousePos( ) };
|
|
if ( mp.type == E_MousePosType::NONE ) {
|
|
if ( io.MouseDown[ 0 ] ) {
|
|
sync.setTime( mTime );
|
|
dnd = E_DNDType::TIME;
|
|
} else if ( io.MouseDown[ 1 ] ) {
|
|
firstZoomPixel = mPixels;
|
|
dnd = E_DNDType::ZOOM;
|
|
curZoomPixel = mPixels;
|
|
|
|
}
|
|
} else if ( mp.type == E_MousePosType::TRACK ) {
|
|
auto const& dTrack{ dspTracks[ mp.index ] };
|
|
if ( io.MouseDown[ 0 ] || io.MouseDown[ 1 ] ) {
|
|
selId = dTrack.id;
|
|
selSegment = decltype( selSegment ){};
|
|
selPoint = decltype( selPoint ){};
|
|
sub = E_SubWindow::SW_TRACK;
|
|
dnd = E_DNDType::CLICK;
|
|
}
|
|
} else if ( mp.type == E_MousePosType::SEGMENT ) {
|
|
auto const& dSeg{ dspSegments[ mp.index ] };
|
|
auto const& dTrack{ dspTracks[ dSeg.track ] };
|
|
if ( io.MouseDown[ 0 ] || io.MouseDown[ 1 ] ) {
|
|
selId = dTrack.id;
|
|
selSegment = dSeg.seg;
|
|
selPoint = decltype( selPoint ){};
|
|
sub = E_SubWindow::SW_SEGMENT;
|
|
dnd = E_DNDType::CLICK;
|
|
}
|
|
} else if ( mp.type == E_MousePosType::POINT ) {
|
|
auto const& dPoint{ dspPoints[ mp.index ] };
|
|
auto const& dSeg{ dspSegments[ dPoint.seg ] };
|
|
auto const& dTrack{ dspTracks[ dSeg.track ] };
|
|
if ( io.MouseDown[ 0 ] || io.MouseDown[ 1 ] ) {
|
|
selId = dTrack.id;
|
|
selSegment = dSeg.seg;
|
|
selPoint = dPoint.index;
|
|
const bool pdnd{ io.MouseDown[ 0 ]
|
|
&& ( dPoint.index != 0 || dSeg.seg != 0 ) };
|
|
if ( pdnd ) {
|
|
assert( selUpdate == E_ChangeType::NONE );
|
|
selPointDnDStart = selPointDnDCur = mPixels;
|
|
if ( selId->isOverride ) {
|
|
auto const& names{ sync.getOverride( selId->id )->inputNames( ) };
|
|
const auto ni{ names.size( ) };
|
|
for ( auto i = 0u ; i < ni ; i ++ ) {
|
|
selUpdatingOriginals.add( *sync.getCurve( names[ i ] ) );
|
|
}
|
|
} else {
|
|
selUpdatingOriginals.add( *sync.getCurve( selId->id ) );
|
|
}
|
|
selUpdate = E_ChangeType::POINT_DND;
|
|
dnd = E_DNDType::POINT;
|
|
} else {
|
|
dnd = E_DNDType::CLICK;
|
|
}
|
|
sub = E_SubWindow::SW_POINT;
|
|
}
|
|
}
|
|
}
|
|
|
|
void T_SyncViewImpl_::computeMetrics(
|
|
const float innerWidth ) noexcept
|
|
{
|
|
auto& sync( Common::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 ( checkLockMode ) {
|
|
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 , units * ( totalPixels - innerWidth ) / totalPixels );
|
|
}
|
|
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 );
|
|
|
|
checkLockMode = 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( UI::Main( ).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 };
|
|
TimeToString_( buffer , sizeof( buffer ) , time );
|
|
RenderTextClipped( taStart , taEnd , buffer , nullptr , nullptr ,
|
|
ImVec2{ .5f , .05f + ( ( bar % 2 ) ? .9f : 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 ( dnd == E_DNDType::ZOOM ) {
|
|
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
|
|
dspTracks.clear( );
|
|
dspSegments.clear( );
|
|
dspPoints.clear( );
|
|
if ( sTracks.size( ) != 0 ) {
|
|
sequencerTracks( inner );
|
|
}
|
|
|
|
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_::sequencerTracks(
|
|
ImRect const& container ) noexcept
|
|
{
|
|
ImRect bb{ container };
|
|
bb.Min.y += TrackPadding - vScroll;
|
|
bb.Max.y = bb.Min.y + TrackHeight;
|
|
|
|
float hue{ 0.12f };
|
|
auto& sync{ Common::Sync( ) };
|
|
|
|
const auto nc{ sTracks.size( ) };
|
|
for ( auto i = 0u ; i < nc ; i ++ ) {
|
|
auto const& id{ sTracks[ i ] };
|
|
auto* const curve{ sync.getCurve(
|
|
sTracks[ i ].isOverride
|
|
? sync.getOverride( id.id )->inputNames( )[ 0 ]
|
|
: id.id ) };
|
|
|
|
if ( showLabels ) {
|
|
using namespace ImGui;
|
|
stringBuffer.clear( );
|
|
if ( id.isOverride ) {
|
|
stringBuffer << "[O] "
|
|
<< sync.getOverride( id.id )->fullTitle( );
|
|
} else {
|
|
stringBuffer << "[I] " << id.id;
|
|
}
|
|
PushStyleColor( ImGuiCol_Text ,
|
|
ColorHSVAToU32( hue , .05f , 1.f , 1.f ) );
|
|
char const* const tStart{ stringBuffer.data( ) };
|
|
char const* const taEnd{ tStart + stringBuffer.size( ) };
|
|
const ImVec2 ts{ CalcTextSize( tStart , taEnd ) };
|
|
RenderTextClipped( bb.Min + ImVec2{ 20 , 0 } ,
|
|
bb.Min + ImVec2{ 20 + ts.x , TrackLabelHeight } ,
|
|
tStart , taEnd , &ts , ImVec2{ 0.f , .5f } ,
|
|
&container );
|
|
PopStyleColor( );
|
|
bb.Min.y += TrackLabelHeight + TrackPadding;
|
|
bb.Max.y += TrackLabelHeight + TrackPadding;
|
|
}
|
|
|
|
sequencerTrack( hue , bb , container , id , curve );
|
|
bb.Min.y += TrackHeight + 2 * TrackPadding;
|
|
bb.Max.y += TrackHeight + 2 * TrackPadding;
|
|
hue = fmodf( hue + .17f , 1.f );
|
|
}
|
|
}
|
|
|
|
void T_SyncViewImpl_::sequencerTrack(
|
|
float& hue ,
|
|
ImRect const& bb ,
|
|
ImRect const& container ,
|
|
T_SyncTrackId const& id ,
|
|
T_SyncCurve const* curve ) noexcept
|
|
{
|
|
// Don't display if the track is fully hidden
|
|
if ( !container.Overlaps( bb ) ) {
|
|
return;
|
|
}
|
|
const ImRect bar{
|
|
ImVec2{ ImFloor( bb.Min.x ) ,
|
|
ImFloor( ImMax( container.Min.y , bb.Min.y ) ) } ,
|
|
ImVec2{ ImFloor( bb.Max.x ) - 1 ,
|
|
ImFloor( ImMin( container.Max.y , bb.Max.y ) ) }
|
|
};
|
|
|
|
// Add track display record
|
|
const auto dTrackIdx{ dspTracks.size( ) };
|
|
auto& dTrack{ dspTracks.addNew( ) };
|
|
dTrack.id = id;
|
|
dTrack.dispSegs = 0;
|
|
dTrack.firstSeg = dspSegments.size( );
|
|
dTrack.area = bb;
|
|
|
|
// Compute colors
|
|
using namespace ImGui;
|
|
auto const& mp{ GetIO( ).MousePos };
|
|
const bool sCurve{ selId && id == *selId };
|
|
const bool barHovered{ bar.Contains( mp ) };
|
|
const float scv{ sCurve ? 1.f : ( barHovered ? .85f : .7f ) };
|
|
const auto bgColor{ ColorHSVAToU32( hue , .25f , scv , .25f ) } ,
|
|
borderColor{ ColorHSVAToU32( hue , .5f , scv , 1.f ) };
|
|
const uint32_t segColors[] = {
|
|
ColorHSVAToU32( hue - .03f , .4f , .7f , 1.f ) ,
|
|
ColorHSVAToU32( hue + .03f , .4f , .7f , 1.f ) ,
|
|
ColorHSVAToU32( hue - .03f , .4f , .85f , 1.f ) ,
|
|
ColorHSVAToU32( hue + .03f , .4f , .85f , 1.f ) ,
|
|
ColorHSVAToU32( hue - .03f , .4f , 1.f , 1.f ) ,
|
|
ColorHSVAToU32( hue + .03f , .4f , 1.f , 1.f ) ,
|
|
};
|
|
|
|
// Draw the bar itself
|
|
auto* const dl{ GetWindowDrawList( ) };
|
|
dl->AddRectFilled( bar.Min , bar.Max , bgColor );
|
|
if ( container.Contains( bb.GetTL( ) ) ) {
|
|
dl->AddLine( bar.GetTL( ) , bar.GetTR( ) , borderColor );
|
|
}
|
|
if ( container.Contains( bb.GetBL( ) ) ) {
|
|
dl->AddLine( bar.GetBL( ) , bar.GetBR( ) , borderColor );
|
|
}
|
|
// Left only has a border if this is the start
|
|
if ( startPos == 0.f ) {
|
|
dl->AddLine( bar.GetTL( ) , bar.GetBL( ) , borderColor );
|
|
}
|
|
// Right has a border if the end is being displayed.
|
|
if ( startPixel + container.GetWidth( ) >= totalPixels ) {
|
|
dl->AddLine( bar.GetTR( ) , bar.GetBR( ) , borderColor );
|
|
}
|
|
dl->PushClipRect( bar.Min , bar.Max );
|
|
|
|
// If there's a curve, go through all segments
|
|
const auto units{ Common::Sync( ).durationUnits( ) };
|
|
const bool useCopy{ sCurve && curve && !selUpdatingCopies.empty( ) };
|
|
const auto nSeg{ curve
|
|
? ( useCopy ? selUpdatingCopies[ 0 ] : *curve ).segments.size( )
|
|
: 0u };
|
|
uint32_t segStart{ 0 };
|
|
for ( auto i = 0u ; i < nSeg ; i ++ ) {
|
|
auto const& seg{ ( useCopy ? selUpdatingCopies[ 0 ] : *curve ).segments[ i ] };
|
|
const auto segDur{ [&](){
|
|
auto t{ 0u };
|
|
for ( auto d : seg.durations ) {
|
|
t += d;
|
|
}
|
|
return t;
|
|
}() };
|
|
const float xStart{ ImFloor( container.Min.x + ( segStart - startPos ) * totalPixels / units ) };
|
|
const float xEnd{ xStart + ImFloor( segDur * totalPixels / units ) };
|
|
const ImRect segFull{
|
|
ImVec2{ xStart , bar.Min.y + 1 } ,
|
|
ImVec2{ xEnd , bar.Max.y }
|
|
};
|
|
segStart += segDur;
|
|
if ( !segFull.Overlaps( bar ) ) {
|
|
continue;
|
|
}
|
|
|
|
// Add segment to displayed list
|
|
const bool sSegment{ sCurve && selSegment && *selSegment == i };
|
|
const bool segHovered{ segFull.Contains( mp ) };
|
|
const auto color{ segColors[ i % 2 + ( sSegment ? 4
|
|
: ( segHovered ? 2 : 0 ) ) ] };
|
|
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 }
|
|
};
|
|
dl->AddRectFilled( segBar.Min , segBar.Max , color );
|
|
dl->PushClipRect( segBar.Min , segBar.Max );
|
|
|
|
// Handle points
|
|
const auto nd{ seg.durations.size( ) };
|
|
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
|
|
};
|
|
const bool sPoint{ sSegment && selPoint && *selPoint == j };
|
|
const bool hpt{ !sPoint && segHovered
|
|
&& ImLengthSqr( mp - ctr ) <= 2.25 * PointRadiusSqr };
|
|
dl->AddCircleFilled( ctr , PointRadius ,
|
|
sPoint ? ColPointSelected
|
|
: ( hpt ? ColPointHovered : ColPointNormal ) );
|
|
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( );
|
|
}
|
|
|
|
dl->PopClipRect( );
|
|
}
|
|
|
|
/*----------------------------------------------------------------------------*/
|
|
|
|
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
|
|
auto& sync{ Common::Sync( ) };
|
|
auto const* const ovr{ track.id.isOverride
|
|
? sync.getOverride( track.id.id )
|
|
: nullptr
|
|
};
|
|
auto const* const curve{ sync.getCurve(
|
|
track.id.isOverride
|
|
? ovr->inputNames( )[ 0 ]
|
|
: track.id.id ) };
|
|
assert( mp.type == E_MousePosType::TRACK || curve != nullptr );
|
|
|
|
// Time offset
|
|
const float dTime( [&](){
|
|
if ( point ) {
|
|
auto tDur{ .0f };
|
|
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( ) };
|
|
for ( auto j = 0u ; j < nd ; j ++ ) {
|
|
tDur += s.durations[ j ];
|
|
}
|
|
}
|
|
return tDur * sync.durationUnitSize( );
|
|
}
|
|
return time;
|
|
}() );
|
|
const float dUTime{ dTime / sync.durationUnitSize( ) };
|
|
char buffer[ 12 ];
|
|
TimeToString_( buffer , sizeof( buffer ) , dTime );
|
|
|
|
stringBuffer.clear( )
|
|
<< ( track.id.isOverride ? ovr->title( ) : track.id.id )
|
|
<< ' '
|
|
<< ( track.id.isOverride ? "(override)" : "(input)" )
|
|
<< '\n';
|
|
if ( mp.type == E_MousePosType::TRACK ) {
|
|
stringBuffer << "No segment";
|
|
} else if ( ovr ) {
|
|
if ( mp.type == E_MousePosType::SEGMENT ) {
|
|
int cType{ -1 };
|
|
for ( auto i = 0u ; i < ovr->inputNames( ).size( ) ; i ++ ) {
|
|
auto const& iName{ ovr->inputNames( )[ i ] };
|
|
auto const* const c{ sync.getCurve( iName ) };
|
|
if ( i == 0 ) {
|
|
cType = c->segments[ seg->seg ].type;
|
|
} else if ( cType != c->segments[ seg->seg ].type ) {
|
|
cType = -1;
|
|
}
|
|
}
|
|
if ( cType == -1 ) {
|
|
stringBuffer << "Various segment types";
|
|
} else {
|
|
stringBuffer << "Segment type: "
|
|
<< T_SyncSegment::E_SegmentType( cType );
|
|
}
|
|
} else {
|
|
stringBuffer << "Point index " << point->index;
|
|
}
|
|
|
|
} 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 << '\n';
|
|
if ( track.id.isOverride ) {
|
|
stringBuffer << "\nInput values:";
|
|
for ( auto i = 0u ; i < ovr->inputNames( ).size( ) ; i ++ ) {
|
|
auto const& iName{ ovr->inputNames( )[ i ] };
|
|
auto const* const c{ sync.getCurve( iName ) };
|
|
const float value{ point
|
|
? c->segments[ seg->seg ].values[ point->index ]
|
|
: ( c ? c->computeValue( dUTime )
|
|
: sync.inputs( )[ ovr->inputPositions( )[ i ] ] ) };
|
|
stringBuffer << "\n - " << iName << ": " << value;
|
|
}
|
|
} else {
|
|
const float value{ point
|
|
? curve->segments[ seg->seg ].values[ point->index ]
|
|
: ( curve ? curve->computeValue( dUTime )
|
|
: sync.inputs( )[ sync.inputPos( track.id.id ) ] ) };
|
|
stringBuffer << "Value: " << value;
|
|
}
|
|
|
|
using namespace ImGui;
|
|
stringBuffer << '\0';
|
|
BeginTooltip( );
|
|
Text( "%s" , stringBuffer.data( ) );
|
|
EndTooltip( );
|
|
}
|
|
|
|
bool T_SyncViewImpl_::handlePointDrag(
|
|
const float mPixels ,
|
|
bool mouseDown ,
|
|
bool shiftPressed ) noexcept
|
|
{
|
|
auto& sync( Common::Sync( ) );
|
|
|
|
selPointDnDCur = mPixels;
|
|
const float diff{ selPointDnDCur - selPointDnDStart };
|
|
const int32_t diffUnits{ int32_t( round( diff * sync.durationUnits( ) / totalPixels ) ) };
|
|
const bool moved{ fabsf( diff ) >= 2 && abs( diffUnits ) > 0 };
|
|
const auto ni{ selUpdatingCopies.size( ) };
|
|
const auto sid{ *selSegment };
|
|
const auto pid{ *selPoint };
|
|
assert( pid > 0 || sid > 0 );
|
|
const bool isLastPoint{ [&](){
|
|
auto const& s{ selUpdatingOriginals[ 0 ].segments };
|
|
return sid == s.size( ) - 1 && pid == s[ sid ].durations.size( );
|
|
}( ) };
|
|
|
|
if ( moved && ( shiftPressed || isLastPoint ) ) {
|
|
// Increase/decrease duration
|
|
selUpdatingCopies = selUpdatingOriginals;
|
|
for ( auto i = 0u ; i < ni ; i ++ ) {
|
|
auto& copy{ selUpdatingCopies[ i ] };
|
|
auto& seg{ copy.segments[ sid ] };
|
|
auto& d{ pid == 0
|
|
? copy.segments[ sid - 1 ].durations.last( )
|
|
: seg.durations[ pid - 1 ] };
|
|
d = std::max( 1 , diffUnits + int32_t( d ) );
|
|
sync.setCurve( copy );
|
|
}
|
|
|
|
} else if ( moved ) {
|
|
// Move the point but keep the sum of before/after durations
|
|
// a constant
|
|
selUpdatingCopies = selUpdatingOriginals;
|
|
for ( auto i = 0u ; i < ni ; i ++ ) {
|
|
auto& copy{ selUpdatingCopies[ i ] };
|
|
auto& seg{ copy.segments[ sid ] };
|
|
|
|
auto& d0{ pid == 0
|
|
? copy.segments[ sid - 1 ].durations.last( )
|
|
: seg.durations[ pid - 1 ] };
|
|
auto& d1{ pid == seg.durations.size( )
|
|
? copy.segments[ sid + 1 ].durations[ 0 ]
|
|
: seg.durations[ pid ]
|
|
};
|
|
|
|
const int32_t mmNeg( 1 - d0 ) , mmPos( d1 - 1 );
|
|
const int32_t diff{ diffUnits < mmNeg ? mmNeg
|
|
: ( diffUnits > mmPos ? mmPos : diffUnits ) };
|
|
d0 += diff;
|
|
d1 -= diff;
|
|
sync.setCurve( copy );
|
|
}
|
|
|
|
} else {
|
|
selUpdatingCopies.clear( );
|
|
for ( auto i = 0u ; i < ni ; i ++ ) {
|
|
sync.setCurve( selUpdatingOriginals[ i ] );
|
|
}
|
|
}
|
|
|
|
if ( mouseDown ) {
|
|
return true;
|
|
}
|
|
|
|
assert( selUpdate == E_ChangeType::POINT_DND );
|
|
selUpdate = E_ChangeType::NONE;
|
|
if ( moved ) {
|
|
auto& undo{ dynamic_cast< T_UndoSyncChanges& >(
|
|
Common::Undo( ).add< T_UndoSyncChanges >( ) ) };
|
|
for ( auto i = 0u ; i < ni ; i ++ ) {
|
|
undo.curveReplacement( std::move( selUpdatingOriginals[ i ] ) ,
|
|
std::move( selUpdatingCopies[ i ] ) );
|
|
}
|
|
} else {
|
|
for ( auto i = 0u ; i < ni ; i ++ ) {
|
|
sync.setCurve( std::move( selUpdatingOriginals[ i ] ) );
|
|
}
|
|
}
|
|
selUpdatingOriginals.clear( );
|
|
selUpdatingCopies.clear( );
|
|
return false;
|
|
}
|
|
|
|
/*----------------------------------------------------------------------------*/
|
|
|
|
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 * 2.25 ) {
|
|
return T_MousePos{ E_MousePosType::POINT ,
|
|
seg.firstPoint + k };
|
|
}
|
|
}
|
|
return T_MousePos{ E_MousePosType::SEGMENT ,
|
|
track.firstSeg + j };
|
|
}
|
|
return T_MousePos{ E_MousePosType::TRACK , i };
|
|
}
|
|
return T_MousePos{ E_MousePosType::NONE , 0 };
|
|
}
|
|
|
|
/*----------------------------------------------------------------------------*/
|
|
|
|
void T_SyncViewImpl_::displayTrackSelectorWindow( ) 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_( "Overrides" , sub == SW_OVERRIDE_SELECTOR , buttonWidth ) ) {
|
|
sub = tsSelectedTab = SW_OVERRIDE_SELECTOR;
|
|
}
|
|
SameLine( 0 );
|
|
if ( FakeTab_( "Individual inputs" , sub == SW_INPUT_SELECTOR , buttonWidth ) ) {
|
|
sub = tsSelectedTab = SW_INPUT_SELECTOR;
|
|
}
|
|
|
|
// Content
|
|
switch ( sub ) {
|
|
case SW_INPUT_SELECTOR:
|
|
displayInputSelector( );
|
|
break;
|
|
case SW_OVERRIDE_SELECTOR:
|
|
displayOverrideSelector( );
|
|
break;
|
|
default:
|
|
fprintf( stderr , "unexpected bullshit in sync view\n" );
|
|
std::abort( );
|
|
}
|
|
End( );
|
|
}
|
|
|
|
void T_SyncViewImpl_::displayInputSelector( ) noexcept
|
|
{
|
|
using namespace ImGui;
|
|
T_Array< T_String > names{ Common::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{ sInputs.contains( n ) };
|
|
const bool overriden{ present && *sInputs.get( n ) };
|
|
|
|
if ( overriden ) {
|
|
PushDisabled( );
|
|
}
|
|
|
|
bool select{ present };
|
|
stringBuffer.clear( ) << n << '\0';
|
|
if ( Checkbox( stringBuffer.data( ) , &select ) ) {
|
|
const T_SyncTrackId id{ n , false };
|
|
if ( select ) {
|
|
sTracks.add( id );
|
|
sInputs.add( n , false );
|
|
} else {
|
|
sTracks.remove( id );
|
|
sInputs.remove( n );
|
|
}
|
|
}
|
|
|
|
if ( overriden ) {
|
|
PopDisabled( );
|
|
}
|
|
}
|
|
EndChild( );
|
|
}
|
|
|
|
void T_SyncViewImpl_::displayOverrideSelector( ) noexcept
|
|
{
|
|
using namespace ImGui;
|
|
|
|
/*
|
|
* An override can be selected directly if the inputs that are part of
|
|
* it are consistent.
|
|
*
|
|
* If the inputs are not consistent, it will be necessary to reset
|
|
* some of their curves; best option would be to try and be clever, but
|
|
* a temporary measure (resetting all curves to match the structure of
|
|
* the first longest curve) would work.
|
|
*/
|
|
|
|
BeginChild( "content" );
|
|
Common::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( );
|
|
return true;
|
|
}
|
|
return TreeNodeEx( &sos.cTitle[ 0 ] ,
|
|
ImGuiTreeNodeFlags_DefaultOpen );
|
|
}
|
|
if ( ! exit ) {
|
|
return false;
|
|
}
|
|
|
|
auto const& ov{ *element.value< A_SyncOverride* >( ) };
|
|
auto const& id{ ov.id( ) };
|
|
const bool present{ sTracks.contains( T_SyncTrackId{ id , true } ) };
|
|
const bool hasCurves{ !present && areOverrideInputsDisplayed( ov ) };
|
|
const bool consistent{ areOverrideInputsConsistent( ov ) };
|
|
|
|
if ( hasCurves ) {
|
|
PushDisabled( );
|
|
}
|
|
if ( !consistent ) {
|
|
PushStyleColor( ImGuiCol_Text , 0xff0000ff );
|
|
}
|
|
|
|
bool selected{ present };
|
|
if ( Checkbox( ov.title( ) , &selected ) ) {
|
|
overrideTrackToggled( ov , selected );
|
|
}
|
|
|
|
if ( !consistent ) {
|
|
PopStyleColor( );
|
|
}
|
|
if ( hasCurves ) {
|
|
PopDisabled( );
|
|
}
|
|
|
|
return true;
|
|
} );
|
|
EndChild( );
|
|
}
|
|
|
|
bool T_SyncViewImpl_::areOverrideInputsConsistent(
|
|
A_SyncOverride const& ov ) noexcept
|
|
{
|
|
/* A pair of inputs are consistent if
|
|
* - one or both inputs do not have curve information attached;
|
|
* - the durations and segment boundaries of the shortest curve
|
|
* attached to the inputs match the boundaries and durations from the
|
|
* other input's curve OR the last segment is shorter but its end
|
|
* matches the location of one of the points in the corresponding
|
|
* segment of the other curve.
|
|
*/
|
|
auto const& in{ ov.inputNames( ) };
|
|
for ( auto i = 0u ; i < in.size( ) - 1 ; i ++ ) {
|
|
T_SyncCurve const* const c0{
|
|
Common::Sync( ).getCurve( in[ i ] )
|
|
};
|
|
if ( !c0 ) {
|
|
continue;
|
|
}
|
|
for ( auto j = i + 1 ; j < in.size( ) ; j ++ ) {
|
|
T_SyncCurve const* const c1{
|
|
Common::Sync( ).getCurve( in[ j ] )
|
|
};
|
|
if ( !c1 ) {
|
|
continue;
|
|
}
|
|
const auto res{ c0->matches( *c1 ) };
|
|
if ( res == E_SyncCurveMatch::MISMATCH ) {
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool T_SyncViewImpl_::wouldInputsNeedAdjustment(
|
|
A_SyncOverride const& ov ) noexcept
|
|
{
|
|
auto& sync{ Common::Sync( ) };
|
|
auto const& in{ ov.inputNames( ) };
|
|
auto nNulls{ 0u };
|
|
for ( auto i = 0u ; i < in.size( ) - 1 ; i ++ ) {
|
|
T_SyncCurve const* const c0{ sync.getCurve( in[ i ] ) };
|
|
if ( !c0 ) {
|
|
nNulls ++;
|
|
continue;
|
|
}
|
|
for ( auto j = i + 1 ; j < in.size( ) ; j ++ ) {
|
|
T_SyncCurve const* const c1{ sync.getCurve( in[ j ] ) };
|
|
if ( !c1 || c0->matches( *c1 ) != E_SyncCurveMatch::IDENTICAL ) {
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
if ( !sync.getCurve( in[ in.size( ) - 1 ] ) ) {
|
|
nNulls ++;
|
|
}
|
|
return nNulls != 0 && nNulls != in.size( );
|
|
}
|
|
|
|
bool T_SyncViewImpl_::areOverrideInputsDisplayed(
|
|
A_SyncOverride const& ov ) const noexcept
|
|
{
|
|
auto const& in{ ov.inputNames( ) };
|
|
for ( auto i = 0u ; i < in.size( ) ; i ++ ) {
|
|
if ( sInputs.contains( in[ i ] ) ) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void T_SyncViewImpl_::overrideTrackToggled(
|
|
A_SyncOverride const& ov ,
|
|
const bool selected ) noexcept
|
|
{
|
|
auto const& in{ ov.inputNames( ) };
|
|
const T_SyncTrackId id{ ov.id( ) , true };
|
|
|
|
// Handle de-selection
|
|
if ( !selected ) {
|
|
sTracks.remove( id );
|
|
for ( auto i = 0u ; i < in.size( ) ; i ++ ) {
|
|
sInputs.remove( in[ i ] );
|
|
}
|
|
return;
|
|
}
|
|
|
|
// If the override is not consistent, we need to make it so
|
|
SyncEditor::MakeOverrideConsistent( ov.id( ) );
|
|
|
|
sTracks.add( id );
|
|
for ( auto i = 0u ; i < in.size( ) ; i ++ ) {
|
|
sInputs.add( in[ i ] , true );
|
|
}
|
|
}
|
|
|
|
/*----------------------------------------------------------------------------*/
|
|
|
|
void T_SyncViewImpl_::clearSelectedTracks( ) noexcept
|
|
{
|
|
sTracks.clear( );
|
|
sInputs.clear( );
|
|
selId.clear( );
|
|
selSegment.clear( );
|
|
selPoint.clear( );
|
|
}
|
|
|
|
void T_SyncViewImpl_::autoselectTracks( ) noexcept
|
|
{
|
|
clearSelectedTracks( );
|
|
|
|
auto& sync{ Common::Sync( ) };
|
|
sync.visitOverrides( [&]( T_SyncOverrideVisitor::T_Element element , const bool exit ) {
|
|
if ( element.hasType< T_SyncOverrideSection* >( ) || !exit ) {
|
|
return true;
|
|
}
|
|
|
|
auto const& ov{ *element.value< A_SyncOverride* >( ) };
|
|
if ( !areOverrideInputsConsistent( ov ) ) {
|
|
return true;
|
|
}
|
|
if ( wouldInputsNeedAdjustment( ov ) ) {
|
|
return true;
|
|
}
|
|
|
|
auto const& in{ ov.inputNames( ) };
|
|
sTracks.add( T_SyncTrackId{ ov.id( ) , true } );
|
|
for ( auto i = 0u ; i < in.size( ) ; i ++ ) {
|
|
sInputs.add( in[ i ] , true );
|
|
}
|
|
|
|
return true;
|
|
} );
|
|
|
|
for ( auto const& n : sync.inputNames( ) ) {
|
|
if ( !sInputs.contains( n ) ) {
|
|
sTracks.add( T_SyncTrackId{ n , false } );
|
|
sInputs.add( n , false );
|
|
}
|
|
}
|
|
}
|
|
|
|
/*----------------------------------------------------------------------------*/
|
|
|
|
void T_SyncViewImpl_::displayTrackWindow( ) 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( "Selected track" , &displayed , ImGuiWindowFlags_NoCollapse );
|
|
if ( !displayed ) {
|
|
End( );
|
|
sub = SW_NONE;
|
|
return;
|
|
}
|
|
|
|
// Get the curve (or the first curve from the set)
|
|
auto& sync{ Common::Sync( ) };
|
|
auto const* const ovr{ selId->isOverride
|
|
? sync.getOverride( selId->id )
|
|
: nullptr };
|
|
auto const* const curve{ sync.getCurve( selId->isOverride
|
|
? ovr->inputNames( )[ 0 ]
|
|
: selId->id ) };
|
|
|
|
if ( selId->isOverride ) {
|
|
Text( "Override:" );
|
|
SameLine( 110 );
|
|
Text( "%s" , ovr->title( ) );
|
|
|
|
for ( auto i = 0u ; i < ovr->inputNames( ).size( ) ; i ++ ) {
|
|
Text( i == 0 ? "Curve(s):" : "" );
|
|
SameLine( 110 );
|
|
Text( "%s" , ovr->inputNames( )[ i ].toOSString( ).data( ) );
|
|
}
|
|
} else {
|
|
Text( "Curve:" );
|
|
SameLine( 110 );
|
|
Text( "%s" , selId->id.toOSString( ).data( ) );
|
|
if ( !sync.hasInput( selId->id ) ) {
|
|
Text( " " );
|
|
SameLine( 110 );
|
|
Text( "No matching input" );
|
|
}
|
|
}
|
|
|
|
Separator( );
|
|
Text( "Segments:" );
|
|
SameLine( 110 );
|
|
Text( "%d" , curve ? curve->segments.size( ) : 0 );
|
|
Separator( );
|
|
|
|
// Compute total duration
|
|
const uint32_t duration{ [&](){
|
|
uint32_t t{ 0 };
|
|
if ( !curve ) {
|
|
return 0u;
|
|
}
|
|
for ( auto const& s : curve->segments ) {
|
|
for ( auto const& d : s.durations ) {
|
|
t += d;
|
|
}
|
|
}
|
|
return t;
|
|
}() };
|
|
const float tDuration{ duration * sync.durationUnitSize( ) };
|
|
|
|
Text( "Duration:" );
|
|
SameLine( 110 );
|
|
Text( "%d units" , duration );
|
|
|
|
Text( " " );
|
|
SameLine( 110 );
|
|
Text( "%.3f seconds" , tDuration );
|
|
|
|
const float dDuration{ sync.duration( ) };
|
|
if ( tDuration == 0.f ) {
|
|
Text( " " );
|
|
SameLine( 110 );
|
|
Text( "Empty track" );
|
|
} else if ( tDuration == dDuration ) {
|
|
Text( " " );
|
|
SameLine( 110 );
|
|
Text( "Covers the whole demo" );
|
|
} else if ( tDuration > dDuration ) {
|
|
Text( " " );
|
|
SameLine( 110 );
|
|
Text( "Longer than the demo" );
|
|
}
|
|
|
|
if ( ( curve && !curve->segments.empty( ) ) || tDuration < dDuration ) {
|
|
Separator( );
|
|
}
|
|
|
|
if ( curve && !curve->segments.empty( ) ) {
|
|
Text( " " );
|
|
SameLine( 110 );
|
|
if ( Button( "Clear" , ImVec2{ -1 , 0 } ) ) {
|
|
SyncEditor::DeleteCurve( *selId );
|
|
}
|
|
}
|
|
if ( tDuration < dDuration ) {
|
|
Text( " " );
|
|
SameLine( 110 );
|
|
if ( Button( "Append segment" , ImVec2{ -1 , 0 } ) ) {
|
|
const uint32_t ns{ std::max( 1u ,
|
|
( sync.durationUnits( ) - duration ) / 2 ) };
|
|
SyncEditor::AppendSegment( *selId , ns );
|
|
}
|
|
}
|
|
|
|
End( );
|
|
}
|
|
|
|
void T_SyncViewImpl_::displaySegmentWindow( ) 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( "Selected segment" , &displayed , ImGuiWindowFlags_NoCollapse );
|
|
if ( !displayed ) {
|
|
End( );
|
|
sub = SW_NONE;
|
|
return;
|
|
}
|
|
|
|
const uint32_t sid{ *selSegment };
|
|
auto& sync{ Common::Sync( ) };
|
|
auto const* const ovr{ selId->isOverride
|
|
? sync.getOverride( selId->id )
|
|
: nullptr };
|
|
|
|
if ( selId->isOverride ) {
|
|
Text( "Override:" );
|
|
SameLine( 110 );
|
|
Text( "%s" , ovr->title( ) );
|
|
} else {
|
|
Text( "Curve:" );
|
|
SameLine( 110 );
|
|
Text( "%s" , selId->id.toOSString( ).data( ) );
|
|
}
|
|
|
|
Text( "Index:" );
|
|
SameLine( 110 );
|
|
Text( "%d" , sid );
|
|
|
|
Separator( );
|
|
|
|
// Access curve and segment
|
|
auto const* const curve{ sync.getCurve( selId->isOverride
|
|
? ovr->inputNames( )[ 0 ]
|
|
: selId->id ) };
|
|
auto const& segment{ curve->segments[ sid ] };
|
|
|
|
// Find start and duration
|
|
uint32_t start{ 0 } , duration{ 0 };
|
|
for ( auto i = 0u ; i <= sid ; i ++ ) {
|
|
auto const& s{ curve->segments[ i ] };
|
|
auto& tgt{ i == sid ? duration : start };
|
|
for ( auto d : s.durations ) {
|
|
tgt += d;
|
|
}
|
|
}
|
|
const float tStart{ sync.durationUnitSize( ) * start } ,
|
|
tDuration{ sync.durationUnitSize( ) * duration };
|
|
|
|
Text( "Start:" );
|
|
SameLine( 110 );
|
|
Text( "%d units" , start );
|
|
|
|
Text( " " );
|
|
SameLine( 110 );
|
|
Text( "%.3f seconds" , tStart );
|
|
|
|
Text( "End:" );
|
|
SameLine( 110 );
|
|
Text( "%d units" , start + duration );
|
|
|
|
Text( " " );
|
|
SameLine( 110 );
|
|
Text( "%.3f seconds" , tStart + tDuration );
|
|
|
|
Text( "Duration:" );
|
|
SameLine( 110 );
|
|
Text( "%d units" , duration );
|
|
|
|
Text( " " );
|
|
SameLine( 110 );
|
|
Text( "%.3f seconds" , tDuration );
|
|
|
|
// Generate the combo box's data
|
|
static constexpr T_SyncSegment::E_SegmentType types[] = {
|
|
T_SyncSegment::LINEAR , T_SyncSegment::RAMP , T_SyncSegment::SMOOTH
|
|
};
|
|
stringBuffer.clear( ) << "various types" << '\0';
|
|
const auto ts{ stringBuffer.size( ) };
|
|
for ( auto t : types ) {
|
|
stringBuffer << t << '\0';
|
|
}
|
|
stringBuffer << '\0';
|
|
|
|
// Display segment type selectors
|
|
auto const nc{ ovr ? ovr->inputNames( ).size( ) : 1u };
|
|
int change;
|
|
int sTypes[ nc + 1 ];
|
|
Separator( );
|
|
if ( !ovr ) {
|
|
Text( "Type:" );
|
|
SameLine( 110 );
|
|
PushItemWidth( -1 );
|
|
sTypes[ 0 ] = int( segment.type );
|
|
change = Combo( "##type" , &sTypes[ 0 ] , stringBuffer.data( ) + ts ) ? 0 : -1;
|
|
PopItemWidth( );
|
|
} else if ( TreeNode( "Segment types" ) ) {
|
|
change = -1;
|
|
const uint32_t sblen{ stringBuffer.size( ) };
|
|
PushItemWidth( -1 );
|
|
int common{ -1 };
|
|
for ( auto i = 0u ; i < nc ; i ++ ) {
|
|
auto const& iName{ ovr->inputNames( )[ i ] };
|
|
sTypes[ i ] = int( sync.getCurve( iName )->segments[ sid ].type );
|
|
if ( i == 0 ) {
|
|
common = sTypes[ i ];
|
|
} else if ( common != sTypes[ i ] ) {
|
|
common = -1;
|
|
}
|
|
|
|
stringBuffer << iName << '\0';
|
|
Text( "%s" , stringBuffer.data( ) + sblen );
|
|
SameLine( 200 );
|
|
|
|
stringBuffer.truncate( sblen ) << "##type" << i << '\0';
|
|
if ( Combo( stringBuffer.data( ) + sblen , &sTypes[ i ] ,
|
|
stringBuffer.data( ) + ts ) ) {
|
|
change = i;
|
|
}
|
|
stringBuffer.truncate( sblen );
|
|
}
|
|
Separator( );
|
|
|
|
const auto cOffset{ common == -1 ? 0 : ts };
|
|
if ( common == -1 ) {
|
|
common ++;
|
|
}
|
|
Text( "All inputs" );
|
|
SameLine( 200 );
|
|
if ( Combo( "##type-all" , &common , stringBuffer.data( ) + cOffset ) ) {
|
|
sTypes[ nc ] = common - ( cOffset == 0 ? 1 : 0 );
|
|
change = nc;
|
|
}
|
|
|
|
PopItemWidth( );
|
|
TreePop( );
|
|
} else {
|
|
change = -1;
|
|
}
|
|
|
|
Separator( );
|
|
|
|
Text( "Points:" );
|
|
SameLine( 110 );
|
|
Text( "%d" , segment.values.size( ) );
|
|
|
|
if ( !ovr ) {
|
|
// Find min/max
|
|
float sMin{ FLT_MAX } , sMax{ -FLT_MAX };
|
|
for ( auto v : segment.values ) {
|
|
sMin = ImMin( sMin , v );
|
|
sMax = ImMax( sMax , v );
|
|
}
|
|
|
|
Text( "Min. value:" );
|
|
SameLine( 110 );
|
|
Text( "%f" , sMin );
|
|
|
|
Text( "Max. value:" );
|
|
SameLine( 110 );
|
|
Text( "%f" , sMax );
|
|
}
|
|
|
|
Separator( );
|
|
|
|
Text( " " );
|
|
SameLine( 110 );
|
|
if ( Button( "Delete segment" , ImVec2{ -1 , 0 } ) ) {
|
|
if ( curve->segments.size( ) > 1 ) {
|
|
selSegment = ( sid == 0 ? 0 : ( sid - 1 ) );
|
|
} else {
|
|
selSegment = decltype( selSegment ){};
|
|
}
|
|
SyncEditor::DeleteSegment( *selId , sid );
|
|
}
|
|
|
|
if ( change >= 0 && change < int32_t( nc ) ) {
|
|
SyncEditor::SetSegmentType(
|
|
*( ovr ? sync.getCurve( ovr->inputNames( )[ change ] ) : curve ) ,
|
|
sid , T_SyncSegment::E_SegmentType( sTypes[ change ] ) );
|
|
} else if ( change == int32_t( nc ) && sTypes[ nc ] != -1 ) {
|
|
assert( ovr );
|
|
SyncEditor::SetSegmentTypes( *ovr , sid ,
|
|
T_SyncSegment::E_SegmentType( sTypes[ nc ] ) );
|
|
}
|
|
|
|
End( );
|
|
}
|
|
|
|
void T_SyncViewImpl_::displayPointWindow( ) 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( "Selected point" , &displayed , ImGuiWindowFlags_NoCollapse );
|
|
if ( !displayed ) {
|
|
End( );
|
|
sub = SW_NONE;
|
|
return;
|
|
}
|
|
|
|
// Access curve, segment and point
|
|
const uint32_t sid{ *selSegment };
|
|
const uint32_t pid{ *selPoint };
|
|
auto& sync{ Common::Sync( ) };
|
|
auto* const ovr{ selId->isOverride
|
|
? sync.getOverride( selId->id )
|
|
: nullptr };
|
|
auto const* const curve{ selUpdatingCopies.size( )
|
|
? &selUpdatingCopies[ 0 ]
|
|
: sync.getCurve( ovr ? ovr->inputNames( )[ 0 ] : selId->id ) };
|
|
auto const& segment{ curve->segments[ sid ] };
|
|
|
|
if ( selId->isOverride ) {
|
|
Text( "Override:" );
|
|
SameLine( 110 );
|
|
Text( "%s" , ovr->title( ) );
|
|
} else {
|
|
Text( "Curve:" );
|
|
SameLine( 110 );
|
|
Text( "%s" , selId->id.toOSString( ).data( ) );
|
|
}
|
|
|
|
Text( "Segment index:" );
|
|
SameLine( 110 );
|
|
Text( "%d" , sid );
|
|
|
|
Text( "Point index:" );
|
|
SameLine( 110 );
|
|
Text( "%d / %d" , pid , segment.durations.size( ) );
|
|
|
|
Separator( );
|
|
|
|
// Find start and duration
|
|
uint32_t segStart{ 0 } , pointPosRel{ 0 };
|
|
for ( auto i = 0u ; i <= sid ; i ++ ) {
|
|
auto const& s{ curve->segments[ i ] };
|
|
auto& tgt{ i == sid ? pointPosRel : segStart };
|
|
const auto end{ i == sid ? pid : s.durations.size( ) };
|
|
for ( auto j = 0u ; j < end ; j ++ ) {
|
|
tgt += s.durations[ j ];
|
|
}
|
|
}
|
|
const uint32_t pointPosAbs{ segStart + pointPosRel };
|
|
const float tPointPosAbs{ sync.durationUnitSize( ) * pointPosAbs } ,
|
|
tPointPosRel{ sync.durationUnitSize( ) * pointPosRel };
|
|
|
|
Text( "Absolute:" );
|
|
SameLine( 110 );
|
|
Text( "%d units" , pointPosAbs );
|
|
|
|
Text( " " );
|
|
SameLine( 110 );
|
|
Text( "%.3f seconds" , tPointPosAbs );
|
|
|
|
Text( "Relative:" );
|
|
SameLine( 110 );
|
|
Text( "%d units" , pointPosRel );
|
|
|
|
Text( " " );
|
|
SameLine( 110 );
|
|
Text( "%.3f seconds" , tPointPosRel );
|
|
|
|
Separator( );
|
|
|
|
// This is here because the curve pointer might get fucked over by
|
|
// the override control
|
|
const auto canInsertBefore{ pid != 0
|
|
&& segment.durations[ pid - 1 ] > 1 };
|
|
const auto canInsertAfter{ pid != segment.durations.size( )
|
|
&& segment.durations[ pid ] > 1 };
|
|
const auto canDelete{ pid != 0 && pid != segment.durations.size( ) };
|
|
|
|
bool changed;
|
|
float value{ 0 };
|
|
if ( ovr ) {
|
|
selEditor.use( );
|
|
uint32_t i{ 0 };
|
|
changed = (UI::Sync( ).uiFor( *ovr ))(
|
|
*ovr , selEditor , i , stringBuffer );
|
|
if ( Button( "Copy data" , ImVec2{ -1 , 0 } ) && !changed ) {
|
|
auto const& iNames{ ovr->inputNames( ) };
|
|
cpType = ovr->type( );
|
|
cpData.clear( );
|
|
for ( auto i = 0u ; i < iNames.size( ) ; i ++ ) {
|
|
cpData.add( sync.getCurve( iNames[ i ] )->segments[ sid ].values[ pid ] );
|
|
}
|
|
}
|
|
|
|
if ( cpType != ovr->type( ) ) {
|
|
PushDisabled( );
|
|
}
|
|
if ( Button( "Paste data" , ImVec2{ -1 , 0 } ) && !changed ) {
|
|
auto const& iNames{ ovr->inputNames( ) };
|
|
for ( auto i = 0u ; i < iNames.size( ) ; i ++ ) {
|
|
auto c{ *sync.getCurve( iNames[ i ] ) };
|
|
selUpdatingOriginals.add( c );
|
|
c.segments[ sid ].values[ pid ] = cpData[ i ];
|
|
selUpdatingCopies.add( c );
|
|
sync.setCurve( std::move( c ) );
|
|
}
|
|
selUpdate = E_ChangeType::POINT_VALUE;
|
|
}
|
|
if ( cpType != ovr->type( ) ) {
|
|
PopDisabled( );
|
|
}
|
|
|
|
} else {
|
|
Text( "Value:" );
|
|
SameLine( 110 );
|
|
PushItemWidth( -1 );
|
|
value = segment.values[ pid ];
|
|
DragFloat( "##value" , &value , .01f , 0 , 0 , "%.6f" );
|
|
changed = IsItemActive( );
|
|
PopItemWidth( );
|
|
}
|
|
|
|
const bool canUseButtons{ !changed && selUpdate == E_ChangeType::NONE };
|
|
if ( canDelete ) {
|
|
SameLine( 110 );
|
|
if ( Button( "Delete point" , ImVec2{ -1 , 0 } ) && canUseButtons ) {
|
|
SyncEditor::DeletePoint( *selId , sid , pid );
|
|
selPoint.clear( );
|
|
sub = SW_SEGMENT;
|
|
}
|
|
}
|
|
|
|
if ( canInsertAfter || canInsertBefore ) {
|
|
Separator( );
|
|
if ( canInsertBefore ) {
|
|
if ( Button( "Insert before" , ImVec2{ -1 , 0 } ) && canUseButtons ) {
|
|
SyncEditor::InsertPoint( *selId , sid , pid );
|
|
(*selPoint) ++;
|
|
}
|
|
}
|
|
if ( canInsertAfter ) {
|
|
if ( Button( "Insert after" , ImVec2{ -1 , 0 } ) && canUseButtons ) {
|
|
SyncEditor::InsertPoint( *selId , sid , pid + 1 );
|
|
}
|
|
}
|
|
}
|
|
|
|
if ( ovr && changed ) {
|
|
if ( selUpdate == E_ChangeType::NONE ) {
|
|
selUpdate = E_ChangeType::POINT_VALUE;
|
|
} else {
|
|
assert( selUpdate == E_ChangeType::POINT_VALUE );
|
|
}
|
|
|
|
} else if ( changed ) {
|
|
if ( selUpdate == E_ChangeType::NONE ) {
|
|
assert( selUpdatingOriginals.empty( ) );
|
|
assert( selUpdatingCopies.empty( ) );
|
|
selUpdatingOriginals.add( *curve );
|
|
selUpdatingCopies.add( *curve );
|
|
selUpdate = E_ChangeType::POINT_VALUE;
|
|
} else {
|
|
assert( selUpdate == E_ChangeType::POINT_VALUE );
|
|
}
|
|
selUpdatingCopies[ 0 ].segments[ sid ].values[ pid ] = value;
|
|
sync.setCurve( selUpdatingCopies[ 0 ] );
|
|
|
|
} else if ( selUpdate == E_ChangeType::POINT_VALUE ) {
|
|
selUpdate = E_ChangeType::NONE;
|
|
auto& undo{ dynamic_cast< T_UndoSyncChanges& >(
|
|
Common::Undo( ).add< T_UndoSyncChanges >( ) ) };
|
|
for ( auto i = 0u ; i < selUpdatingCopies.size( ) ; i ++ ) {
|
|
undo.curveReplacement( std::move( selUpdatingOriginals[ i ] ) ,
|
|
std::move( selUpdatingCopies[ i ] ) );
|
|
}
|
|
selUpdatingCopies.clear( );
|
|
selUpdatingOriginals.clear( );
|
|
}
|
|
|
|
End( );
|
|
}
|
|
|
|
} // namespace <anon>
|
|
|
|
|
|
/*= T_SyncView ===============================================================*/
|
|
|
|
T_SyncView::T_SyncView( ) noexcept
|
|
: A_PrivateImplementation( new T_SyncViewImpl_( ) )
|
|
{ }
|
|
|
|
bool T_SyncView::display( ) noexcept
|
|
{
|
|
return p< T_SyncViewImpl_ >( ).display( );
|
|
}
|