714 lines
29 KiB
C#
714 lines
29 KiB
C#
using ImGuiNET;
|
||
using T3.Core.Animation;
|
||
using T3.Core.Audio;
|
||
using T3.Core.DataTypes.DataSet;
|
||
using T3.Core.DataTypes.Vector;
|
||
using T3.Core.IO;
|
||
using T3.Core.Operator;
|
||
using T3.Core.Settings;
|
||
using T3.Core.Utils;
|
||
using T3.Editor.Gui.Interaction;
|
||
using T3.Editor.Gui.Interaction.Keyboard;
|
||
using T3.Editor.Gui.Interaction.Timing;
|
||
using T3.Editor.Gui.OutputUi;
|
||
using T3.Editor.Gui.Styling;
|
||
using T3.Editor.Gui.UiHelpers;
|
||
using T3.Editor.Gui.Windows.Layouts;
|
||
using T3.Editor.UiModel;
|
||
using T3.Editor.UiModel.InputsAndTypes;
|
||
using Icon = T3.Editor.Gui.Styling.Icon;
|
||
using Vector2 = System.Numerics.Vector2;
|
||
using Vector4 = System.Numerics.Vector4;
|
||
|
||
// ReSharper disable CompareOfFloatsByEqualityOperator
|
||
|
||
namespace T3.Editor.Gui.Windows.TimeLine;
|
||
|
||
[HelpUiID("ControlBar")]
|
||
internal static class TimeControls
|
||
{
|
||
internal static void HandleTimeControlActions()
|
||
{
|
||
var playback = Playback.Current; // TODO, this should be non-static eventually
|
||
|
||
if (UserActions.TapBeatSync.Triggered())
|
||
BeatTiming.TriggerSyncTap();
|
||
|
||
if (UserActions.TapBeatSyncMeasure.Triggered())
|
||
BeatTiming.TriggerResyncMeasure();
|
||
|
||
if (UserActions.PlaybackJumpToPreviousKeyframe.Triggered())
|
||
UserActionRegistry.QueueAction(UserActions.PlaybackJumpToPreviousKeyframe);
|
||
|
||
if (UserActions.PlaybackJumpToStartTime.Triggered())
|
||
playback.TimeInBars = playback.IsLooping ? playback.LoopRange.Start : 0;
|
||
|
||
if (UserActions.PlaybackJumpToPreviousKeyframe.Triggered())
|
||
UserActionRegistry.QueueAction(UserActions.PlaybackJumpToPreviousKeyframe);
|
||
|
||
{
|
||
//const float editFrameRate = 30;
|
||
|
||
var frameDuration = UserSettings.Config.FrameStepAmount switch
|
||
{
|
||
FrameStepAmount.FrameAt60Fps => 1 / 60f,
|
||
FrameStepAmount.FrameAt30Fps => 1 / 30f,
|
||
FrameStepAmount.FrameAt15Fps => 1 / 15f,
|
||
FrameStepAmount.Bar => (float)playback.SecondsFromBars(1),
|
||
FrameStepAmount.Beat => (float)playback.SecondsFromBars(1 / 4f),
|
||
FrameStepAmount.Tick => (float)playback.SecondsFromBars(1 / 16f),
|
||
_ => 1
|
||
};
|
||
|
||
var editFrameRate = 1 / frameDuration;
|
||
|
||
// Step to previous frame
|
||
if (UserActions.PlaybackPreviousFrame.Triggered())
|
||
{
|
||
var rounded = Math.Round(playback.TimeInSecs * editFrameRate) / editFrameRate;
|
||
playback.TimeInSecs = rounded - frameDuration;
|
||
}
|
||
|
||
if (UserActions.PlaybackJumpBack.Triggered())
|
||
{
|
||
playback.TimeInBars -= 1;
|
||
}
|
||
|
||
// Step to next frame
|
||
if (UserActions.PlaybackNextFrame.Triggered())
|
||
{
|
||
var rounded = Math.Round(playback.TimeInSecs * editFrameRate) / editFrameRate;
|
||
playback.TimeInSecs = rounded + frameDuration;
|
||
}
|
||
}
|
||
|
||
// Play backwards with increasing speed
|
||
if (UserActions.PlaybackBackwards.Triggered())
|
||
{
|
||
Log.Debug("Backwards triggered with speed " + playback.PlaybackSpeed);
|
||
if (playback.PlaybackSpeed >= 0)
|
||
{
|
||
playback.PlaybackSpeed = -1;
|
||
}
|
||
else if (playback.PlaybackSpeed > -16)
|
||
{
|
||
playback.PlaybackSpeed *= 2;
|
||
}
|
||
}
|
||
|
||
// Play forward with increasing speed
|
||
if (UserActions.PlaybackForward.Triggered())
|
||
{
|
||
if (playback.PlaybackSpeed <= 0)
|
||
{
|
||
_lastPlaybackStartTime = playback.TimeInBars;
|
||
playback.PlaybackSpeed = 1;
|
||
}
|
||
else if (playback.PlaybackSpeed < 16) // Bass can't play much faster anyways
|
||
{
|
||
playback.PlaybackSpeed *= 2;
|
||
}
|
||
}
|
||
|
||
if (UserActions.PlaybackForwardHalfSpeed.Triggered())
|
||
{
|
||
if (playback.PlaybackSpeed is > 0 and < 1f)
|
||
playback.PlaybackSpeed *= 0.5f;
|
||
else
|
||
playback.PlaybackSpeed = 0.5f;
|
||
}
|
||
|
||
// Stop as separate keyboard action
|
||
if (UserActions.PlaybackStop.Triggered())
|
||
{
|
||
playback.PlaybackSpeed = 0;
|
||
if (UserSettings.Config.ResetTimeAfterPlayback)
|
||
playback.TimeInBars = _lastPlaybackStartTime;
|
||
}
|
||
|
||
if (UserActions.PlaybackToggle.Triggered())
|
||
{
|
||
if (playback.PlaybackSpeed == 0)
|
||
{
|
||
playback.PlaybackSpeed = 1;
|
||
_lastPlaybackStartTime = playback.TimeInBars;
|
||
}
|
||
else
|
||
{
|
||
playback.PlaybackSpeed = 0;
|
||
if (UserSettings.Config.ResetTimeAfterPlayback)
|
||
playback.TimeInBars = _lastPlaybackStartTime;
|
||
}
|
||
}
|
||
|
||
if (UserActions.SetStartTime.Triggered())
|
||
{
|
||
Playback.Current.IsLooping = true;
|
||
Playback.Current.LoopRange.Start = (float)Playback.Current.TimeInBars;
|
||
if(Playback.Current.LoopRange.End < Playback.Current.LoopRange.Start)
|
||
Playback.Current.LoopRange.End = Playback.Current.LoopRange.Start + 4;
|
||
}
|
||
|
||
if (UserActions.SetEndTime.Triggered())
|
||
{
|
||
Playback.Current.IsLooping = true;
|
||
Playback.Current.LoopRange.End = (float)Playback.Current.TimeInBars;
|
||
if(Playback.Current.LoopRange.Start > Playback.Current.LoopRange.End)
|
||
Playback.Current.LoopRange.Start = Playback.Current.LoopRange.End - 4;
|
||
}
|
||
|
||
if (UserActions.PlaybackJumpToNextKeyframe.Triggered())
|
||
UserActionRegistry.QueueAction(UserActions.PlaybackJumpToNextKeyframe);
|
||
|
||
if (UserActions.PlaybackJumpToPreviousKeyframe.Triggered())
|
||
UserActionRegistry.QueueAction(UserActions.PlaybackJumpToPreviousKeyframe);
|
||
|
||
|
||
}
|
||
|
||
internal static void DrawTimeControls(TimeLineCanvas timeLineCanvas, Instance composition)
|
||
{
|
||
var playback = Playback.Current;
|
||
|
||
// Settings
|
||
PlaybackUtils.FindCompositionSettingsForInstance(composition, out var compositionWithSettings, out var settings);
|
||
var opHasSettings = compositionWithSettings == composition;
|
||
|
||
|
||
// if (CustomComponents.IconButton(CoreSettings.Config.AudioMuted ? Icon.ToggleAudioOff : Icon.ToggleAudioOn,
|
||
// ControlSize,
|
||
// CoreSettings.Config.AudioMuted
|
||
// ? CustomComponents.ButtonStates.NeedsAttention
|
||
// : CustomComponents.ButtonStates.Default
|
||
// ))
|
||
// {
|
||
// CoreSettings.Config.AudioMuted = !CoreSettings.Config.AudioMuted;
|
||
// AudioEngine.SetMute(CoreSettings.Config.AudioMuted);
|
||
// }
|
||
|
||
if (CustomComponents.IconButton(Icon.Settings, ControlSize, opHasSettings
|
||
? CustomComponents.ButtonStates.Emphasized
|
||
: CustomComponents.ButtonStates.Default))
|
||
{
|
||
WindowManager.ToggleInstanceVisibility<ProjectSettingsWindow>();
|
||
}
|
||
|
||
CustomComponents.TooltipForLastItem("Project Settings",
|
||
"Switch between soundtrack and VJ modes. Control BPM and other inputs.");
|
||
|
||
ImGui.SameLine();
|
||
|
||
// Current Time
|
||
var delta = 0.0;
|
||
string formattedTime = "";
|
||
switch (UserSettings.Config.TimeDisplayMode)
|
||
{
|
||
case TimeFormat.TimeDisplayModes.Bars:
|
||
formattedTime = TimeFormat.FormatTimeInBars(playback.TimeInBars, 0);
|
||
break;
|
||
|
||
case TimeFormat.TimeDisplayModes.Secs:
|
||
var ts = TimeSpan.FromSeconds(playback.TimeInSecs);
|
||
|
||
formattedTime = ts.Hours > 0
|
||
? ts.ToString(@"hh\:mm\:ss\:ff")
|
||
: ts.ToString(@"mm\:ss\:ff");
|
||
break;
|
||
|
||
case TimeFormat.TimeDisplayModes.F30:
|
||
var frames = playback.TimeInSecs * 30;
|
||
formattedTime = $"{frames:0}f ";
|
||
break;
|
||
|
||
case TimeFormat.TimeDisplayModes.F60:
|
||
var frames60 = playback.TimeInSecs * 60;
|
||
formattedTime = $"{frames60:0}f ";
|
||
break;
|
||
}
|
||
|
||
ImGui.PushStyleColor(ImGuiCol.Text, UiColors.TextMuted.Rgba);
|
||
if (JogDial(formattedTime, ref delta, new Vector2(StandardWidth, ControlSize.Y)))
|
||
{
|
||
playback.PlaybackSpeed = 0;
|
||
playback.TimeInBars += delta;
|
||
if (UserSettings.Config.TimeDisplayMode == TimeFormat.TimeDisplayModes.F30)
|
||
{
|
||
playback.TimeInSecs = Math.Floor(playback.TimeInSecs * 30) / 30;
|
||
}
|
||
}
|
||
ImGui.PopStyleColor();
|
||
|
||
if(ImGui.IsItemHovered())
|
||
CustomComponents.TooltipForLastItem($"Current playtime at {settings.Playback.Bpm:0.0} BPM.", "Click mode button to toggle between timeline formats.");
|
||
|
||
ImGui.SameLine();
|
||
|
||
// Time Mode with context menu
|
||
ImGui.PushStyleColor(ImGuiCol.Text, UiColors.TextMuted.Rgba);
|
||
if (ImGui.Button(UserSettings.Config.TimeDisplayMode.ToString(), ControlSize * new Vector2(1.5f,1)))
|
||
{
|
||
UserSettings.Config.TimeDisplayMode =
|
||
(TimeFormat.TimeDisplayModes)(((int)UserSettings.Config.TimeDisplayMode + 1) % Enum.GetNames(typeof(TimeFormat.TimeDisplayModes)).Length);
|
||
}
|
||
|
||
ImGui.PopStyleColor();
|
||
|
||
CustomComponents.TooltipForLastItem("Timeline format",
|
||
"Click to toggle through BPM, Frames and Normal time modes");
|
||
|
||
ImGui.SameLine();
|
||
|
||
// Idle motion
|
||
{
|
||
ImGui.PushStyleColor(ImGuiCol.Text, UserSettings.Config.EnableIdleMotion
|
||
? UiColors.TextDisabled
|
||
: new Vector4(0, 0, 0, 0.5f));
|
||
|
||
// Create invisible button with same size as icon buttons would have
|
||
|
||
if (ImGui.Button("##idleMotionToggle", ControlSize))
|
||
{
|
||
UserSettings.Config.EnableIdleMotion = !UserSettings.Config.EnableIdleMotion;
|
||
}
|
||
|
||
// Tooltip (same as before)
|
||
CustomComponents.TooltipForLastItem("Idle Motion - Keeps beat time running",
|
||
"This will keep updating the output [Time]\nwhich is useful for procedural animation and syncing.");
|
||
|
||
var drawList = ImGui.GetWindowDrawList();
|
||
var min = ImGui.GetItemRectMin();
|
||
var max = ImGui.GetItemRectMax();
|
||
var center = (min + max) * 0.5f;
|
||
|
||
// Draw beat grid background
|
||
const int cellCount = 4;
|
||
var cellSize = CellSize;
|
||
var gridOffset = center - GridSize;
|
||
|
||
// Pre-calculate the cell size minus 1 for the rect size
|
||
var rectSize = new Vector2(cellSize - 1, cellSize - 1);
|
||
|
||
var gridColor = UserSettings.Config.EnableIdleMotion ? UiColors.BackgroundFull.Fade(0.2f) : UiColors.ForegroundFull.Fade(0.1f);
|
||
for (int x = 0; x < cellCount; x++)
|
||
{
|
||
for (int y = 0; y < cellCount; y++)
|
||
{
|
||
var cellMin = gridOffset + new Vector2(x * cellSize, y * cellSize);
|
||
var cellMax = cellMin + rectSize;
|
||
drawList.AddRectFilled(cellMin, cellMax, gridColor);
|
||
}
|
||
}
|
||
|
||
if (!UserSettings.Config.EnableIdleMotion)
|
||
{
|
||
var diagonal = new Vector2(CellSize, -CellSize) * 2.3f;
|
||
var lineStart = center + diagonal;
|
||
var lineEnd = center - diagonal;
|
||
drawList.AddLine(lineStart,
|
||
lineEnd,
|
||
UiColors.BackgroundPopup.Fade(0.4f),5);
|
||
drawList.AddLine(lineStart,
|
||
lineEnd,
|
||
UiColors.ForegroundFull.Fade(0.1f));
|
||
|
||
}
|
||
|
||
// If idle motion is enabled, draw the animated indicator
|
||
if (UserSettings.Config.EnableIdleMotion)
|
||
{
|
||
var beat = (int)(playback.FxTimeInBars * 4) % 4;
|
||
var beatPulse = (playback.FxTimeInBars * 4) % 4 - beat;
|
||
var bar = (int)(playback.FxTimeInBars) % 4;
|
||
|
||
if (beat < 0)
|
||
beat = 4 + beat;
|
||
|
||
if (bar < 0)
|
||
bar = 4 + bar;
|
||
|
||
var indicatorMin = gridOffset + new Vector2(beat * cellSize, bar * cellSize);
|
||
var indicatorMax = indicatorMin + new Vector2(cellSize - 1, cellSize - 1);
|
||
|
||
drawList.AddRectFilled(indicatorMin, indicatorMax,
|
||
Color.Mix(UiColors.StatusAnimated,
|
||
UiColors.BackgroundFull.Fade(0.3f),
|
||
(float)beatPulse));
|
||
}
|
||
|
||
|
||
|
||
ImGui.PopStyleColor();
|
||
ImGui.SameLine();
|
||
}
|
||
|
||
// MidiIndicator
|
||
{
|
||
var timeSinceLastEvent = Playback.RunTimeInSecs - Math.Max(T3Ui.MidiDataRecording.LastEventTime, T3Ui.OscDataRecording.LastEventTime);
|
||
var flashFactor = MathF.Pow((float)timeSinceLastEvent.Clamp(0, 1) / 1, 0.5f);
|
||
var color = Color.Mix(UiColors.StatusAnimated, UiColors.BackgroundFull.Fade(0.3f), flashFactor);
|
||
|
||
if (CustomComponents.IconButton(Icon.IO, ControlSize, color))
|
||
{
|
||
//T3Ui.MidiStreamRecorder.Reset();
|
||
//DataRecording.ActiveRecordingSet.WriteToFile();
|
||
WindowManager.ToggleInstanceVisibility<IoViewWindow>();
|
||
}
|
||
|
||
if (ImGui.IsItemHovered())
|
||
{
|
||
ImGui.BeginTooltip();
|
||
if (timeSinceLastEvent < 10)
|
||
{
|
||
ImGui.BeginChild("canvas", new Vector2(400, 250));
|
||
|
||
//DataSetOutputUi.DrawDataSet(dataSet);
|
||
_dataSetView.Draw(DataRecording.ActiveRecordingSet);
|
||
ImGui.EndChild();
|
||
}
|
||
else
|
||
{
|
||
ImGui.Text("Midi and OSC input indicator\nClick to open IO window.");
|
||
}
|
||
|
||
ImGui.EndTooltip();
|
||
}
|
||
|
||
ImGui.SameLine();
|
||
}
|
||
|
||
if (settings.Playback.Syncing == CompositionSettings.SyncModes.Tapping)
|
||
{
|
||
var bpm = BeatTiming.Bpm;
|
||
if (SingleValueEdit.Draw(ref bpm, new Vector2(StandardWidth, ControlSize.Y), min: 1, max: 360, clampMin: true, clampMax: true, scale: 0.01f, format: "{0:0.0 BPM}") ==
|
||
InputEditStateFlags.Modified)
|
||
{
|
||
composition.Symbol.GetSymbolUi().FlagAsModified();
|
||
BeatTiming.SetBpmRate(bpm);
|
||
}
|
||
|
||
ImGui.SameLine();
|
||
|
||
ImGui.Button("Sync", SyncButton);
|
||
if (ImGui.IsItemHovered())
|
||
{
|
||
if (ImGui.IsMouseClicked(ImGuiMouseButton.Left))
|
||
{
|
||
if (ImGui.GetIO().KeyCtrl)
|
||
{
|
||
var roundedBpm = Math.Round(BeatTiming.Bpm*2)/2;
|
||
BeatTiming.SetBpmRate((float)roundedBpm);
|
||
}
|
||
else
|
||
{
|
||
BeatTiming.TriggerSyncTap();
|
||
}
|
||
}
|
||
else if (ImGui.IsMouseClicked(ImGuiMouseButton.Right))
|
||
{
|
||
BeatTiming.TriggerResyncMeasure();
|
||
}
|
||
}
|
||
|
||
CustomComponents.TooltipForLastItem("Click on beat to sync. Tap later once to refine. Click right to sync measure.\n"
|
||
+ "Ctrl+Click to round BPM",
|
||
$"Tap: {UserActions.TapBeatSync.ListShortcuts()}\n"
|
||
+ $"Resync: {UserActions.TapBeatSyncMeasure.ListShortcuts()}");
|
||
|
||
ImGui.SameLine();
|
||
}
|
||
else
|
||
{
|
||
// Jump to start
|
||
if (CustomComponents.IconButton(Icon.JumpToRangeStart,
|
||
ControlSize,
|
||
playback.TimeInBars != playback.LoopRange.Start
|
||
? CustomComponents.ButtonStates.Default
|
||
: CustomComponents.ButtonStates.Disabled
|
||
)
|
||
|| UserActions.PlaybackJumpToStartTime.Triggered()
|
||
)
|
||
{
|
||
playback.TimeInBars = playback.IsLooping ? playback.LoopRange.Start : 0;
|
||
}
|
||
|
||
CustomComponents.TooltipForLastItem("Jump to beginning",
|
||
UserActions.PlaybackJumpToStartTime.ListShortcuts());
|
||
|
||
ImGui.SameLine();
|
||
|
||
// Prev Keyframe
|
||
if (CustomComponents.IconButton(Icon.JumpToPreviousKeyframe,
|
||
ControlSize,
|
||
FrameStats.Last.HasKeyframesBeforeCurrentTime
|
||
? CustomComponents.ButtonStates.Default
|
||
: CustomComponents.ButtonStates.Disabled)
|
||
)
|
||
{
|
||
UserActionRegistry.QueueAction(UserActions.PlaybackJumpToPreviousKeyframe);
|
||
}
|
||
|
||
CustomComponents.TooltipForLastItem("Jump to previous keyframe",
|
||
UserActions.PlaybackJumpToPreviousKeyframe.ListShortcuts());
|
||
|
||
ImGui.SameLine();
|
||
|
||
// Play backwards
|
||
if (CustomComponents.IconButton(Icon.PlayBackwards,
|
||
ControlSize,
|
||
playback.PlaybackSpeed < 0
|
||
? CustomComponents.ButtonStates.Activated
|
||
: CustomComponents.ButtonStates.Default
|
||
))
|
||
{
|
||
if (playback.PlaybackSpeed != 0)
|
||
{
|
||
playback.PlaybackSpeed = 0;
|
||
}
|
||
else if (playback.PlaybackSpeed == 0)
|
||
{
|
||
playback.PlaybackSpeed = -1;
|
||
}
|
||
}
|
||
|
||
if (playback.PlaybackSpeed < -1)
|
||
{
|
||
ImGui.GetWindowDrawList().AddText(ImGui.GetItemRectMin() + new Vector2(20, 4), UiColors.ForegroundFull, $"×{-playback.PlaybackSpeed:0}");
|
||
}
|
||
|
||
CustomComponents.TooltipForLastItem("Play backwards",
|
||
"Play backwards (and faster): " +
|
||
UserActions.PlaybackBackwards.ListShortcuts() +
|
||
"\nPrevious frame:" + UserActions.PlaybackPreviousFrame.ListShortcuts());
|
||
|
||
ImGui.SameLine();
|
||
|
||
// Play forward
|
||
if (CustomComponents.IconButton(Icon.PlayForwards,
|
||
ControlSize,
|
||
playback.PlaybackSpeed > 0
|
||
? CustomComponents.ButtonStates.Activated
|
||
: CustomComponents.ButtonStates.Default
|
||
))
|
||
{
|
||
if (Math.Abs(playback.PlaybackSpeed) > 0.001f)
|
||
{
|
||
playback.PlaybackSpeed = 0;
|
||
}
|
||
else if (Math.Abs(playback.PlaybackSpeed) < 0.001f)
|
||
{
|
||
playback.PlaybackSpeed = 1;
|
||
}
|
||
}
|
||
|
||
if (playback.PlaybackSpeed > 1)
|
||
{
|
||
ImGui.GetWindowDrawList().AddText(ImGui.GetItemRectMin() + new Vector2(20, 4), UiColors.ForegroundFull, $"×{playback.PlaybackSpeed:0}");
|
||
}
|
||
|
||
CustomComponents.TooltipForLastItem("Start playback",
|
||
"Play forward (and faster): " +
|
||
UserActions.PlaybackForward.ListShortcuts() +
|
||
"\nPlay half speed (and slower): " +
|
||
UserActions.PlaybackForwardHalfSpeed.ListShortcuts() +
|
||
"\nNext frame:" + UserActions.PlaybackNextFrame.ListShortcuts());
|
||
|
||
ImGui.SameLine();
|
||
|
||
// Next Keyframe
|
||
if (CustomComponents.IconButton(Icon.JumpToNextKeyframe,
|
||
ControlSize,
|
||
FrameStats.Last.HasKeyframesAfterCurrentTime
|
||
? CustomComponents.ButtonStates.Default
|
||
: CustomComponents.ButtonStates.Disabled)
|
||
)
|
||
{
|
||
UserActionRegistry.QueueAction(UserActions.PlaybackJumpToNextKeyframe);
|
||
}
|
||
|
||
CustomComponents.TooltipForLastItem("Jump to next keyframe",
|
||
UserActions.PlaybackJumpToNextKeyframe.ListShortcuts());
|
||
ImGui.SameLine();
|
||
|
||
// // End
|
||
// Loop
|
||
if (CustomComponents.IconButton(Icon.Loop,
|
||
ControlSize,
|
||
playback.IsLooping
|
||
? CustomComponents.ButtonStates.Activated
|
||
: CustomComponents.ButtonStates.Default))
|
||
{
|
||
playback.IsLooping = !playback.IsLooping;
|
||
var loopRangeMatchesTime = playback.LoopRange.IsValid && playback.LoopRange.Contains(playback.TimeInBars);
|
||
if (playback.IsLooping && !loopRangeMatchesTime)
|
||
{
|
||
playback.LoopRange.Start = (float)(playback.TimeInBars - playback.TimeInBars % 4);
|
||
playback.LoopRange.Duration = 4;
|
||
}
|
||
}
|
||
|
||
CustomComponents.TooltipForLastItem("Loop playback", "This will initialize one bar around current time.");
|
||
|
||
ImGui.SameLine();
|
||
|
||
// Curve Mode
|
||
var hasKeyframes = FrameStats.Current.HasKeyframesAfterCurrentTime || FrameStats.Current.HasKeyframesAfterCurrentTime;
|
||
ImGui.PushStyleColor(ImGuiCol.Text, hasKeyframes ? UiColors.Text.Rgba : UiColors.TextMuted);
|
||
if (ImGui.Button(timeLineCanvas.Mode.ToString(), DopeCurve)) //
|
||
{
|
||
timeLineCanvas.Mode = (TimeLineCanvas.Modes)(((int)timeLineCanvas.Mode + 1) % Enum.GetNames(typeof(TimeLineCanvas.Modes)).Length);
|
||
}
|
||
|
||
ImGui.PopStyleColor();
|
||
|
||
CustomComponents.TooltipForLastItem("Toggle keyframe view between Dope sheet and Curve mode.");
|
||
|
||
ImGui.SameLine();
|
||
}
|
||
|
||
// ToggleAudio
|
||
if (CustomComponents.IconButton(CoreSettings.Config.AppMute ? Icon.ToggleAudioOff : Icon.ToggleAudioOn,
|
||
ControlSize,
|
||
CoreSettings.Config.AppMute
|
||
? CustomComponents.ButtonStates.NeedsAttention
|
||
: CustomComponents.ButtonStates.Default
|
||
))
|
||
{
|
||
AudioEngine.SetGlobalMute(!CoreSettings.Config.AppMute);
|
||
}
|
||
|
||
// ToggleHover
|
||
{
|
||
ImGui.SameLine();
|
||
Icon icon;
|
||
string hoverModeTooltip;
|
||
string hoverModeAdditionalTooltip = null;
|
||
CustomComponents.ButtonStates state = CustomComponents.ButtonStates.Emphasized;
|
||
switch (UserSettings.Config.HoverMode)
|
||
{
|
||
case UserSettings.GraphHoverModes.Disabled:
|
||
state = CustomComponents.ButtonStates.Default;
|
||
icon = Icon.HoverPreviewDisabled;
|
||
hoverModeTooltip = "No preview images on hover";
|
||
break;
|
||
case UserSettings.GraphHoverModes.Live:
|
||
icon = Icon.HoverPreviewPlay;
|
||
hoverModeTooltip = "Live Hover Preview - Render explicit thumbnail image.";
|
||
hoverModeAdditionalTooltip = "This can interfere with the rendering of the current output.";
|
||
break;
|
||
default:
|
||
icon = Icon.HoverPreviewSmall;
|
||
hoverModeTooltip = "Last - Show the current state of the operator.";
|
||
hoverModeAdditionalTooltip = "This can be outdated if operator is not require for current output.";
|
||
break;
|
||
}
|
||
|
||
if (CustomComponents.IconButton(icon, ControlSize, state))
|
||
{
|
||
UserSettings.Config.HoverMode =
|
||
(UserSettings.GraphHoverModes)(((int)UserSettings.Config.HoverMode + 1) % Enum.GetNames(typeof(UserSettings.GraphHoverModes)).Length);
|
||
}
|
||
|
||
CustomComponents.TooltipForLastItem(hoverModeTooltip, hoverModeAdditionalTooltip);
|
||
}
|
||
|
||
if (FrameStats.Last.HasAnimatedParameters)
|
||
{
|
||
// Lock all animated parameters
|
||
ImGui.SameLine();
|
||
var state = UserSettings.Config.AutoPinAllAnimations
|
||
? CustomComponents.ButtonStates.Activated
|
||
: CustomComponents.ButtonStates.Default;
|
||
|
||
if (CustomComponents.IconButton(Icon.PinParams, ControlSize, state)
|
||
|| UserActions.ToggleAnimationPinning.Triggered())
|
||
{
|
||
UserSettings.Config.AutoPinAllAnimations = !UserSettings.Config.AutoPinAllAnimations;
|
||
|
||
if (!UserSettings.Config.AutoPinAllAnimations)
|
||
{
|
||
timeLineCanvas.DopeSheetArea.PinnedParametersHashes.Clear();
|
||
}
|
||
}
|
||
CustomComponents.TooltipForLastItem("Keep animated parameters visible",
|
||
"This can be useful when align animations between multiple operators. Toggle again to clear the visible animations.\n\n"
|
||
+ UserActions.ToggleAnimationPinning.ListShortcuts()
|
||
);
|
||
}
|
||
|
||
|
||
ImGui.SameLine();
|
||
}
|
||
|
||
private static double _lastPlaybackStartTime;
|
||
// beat grid optimizations
|
||
private static float _lastUiScaleFactor = -1f;
|
||
private static float _cachedCellSize;
|
||
private static Vector2 _cachedGridSize;
|
||
|
||
private static float CellSize
|
||
{
|
||
get
|
||
{
|
||
if (!(Math.Abs(_lastUiScaleFactor - T3Ui.UiScaleFactor) > 0.001f))
|
||
return _cachedCellSize;
|
||
|
||
_lastUiScaleFactor = T3Ui.UiScaleFactor;
|
||
_cachedCellSize = 4f * T3Ui.UiScaleFactor;
|
||
_cachedGridSize = new Vector2(_cachedCellSize * 4 * 0.5f);
|
||
return _cachedCellSize;
|
||
}
|
||
}
|
||
|
||
private static Vector2 GridSize
|
||
{
|
||
get
|
||
{
|
||
// Trigger cache update if needed
|
||
_ = CellSize;
|
||
return _cachedGridSize;
|
||
}
|
||
}
|
||
// end of beat grid optimizations
|
||
|
||
private static float StandardWidth => 100f * T3Ui.UiScaleFactor;
|
||
public static Vector2 ControlSize => new Vector2(35, 28) * T3Ui.UiScaleFactor;
|
||
private static Vector2 DopeCurve => new Vector2(95, 28) * T3Ui.UiScaleFactor;
|
||
private static Vector2 SyncButton => new Vector2(45, 28) * T3Ui.UiScaleFactor;
|
||
|
||
private static readonly DataSetViewCanvas _dataSetView = new()
|
||
{
|
||
ShowInteraction = false,
|
||
MaxTreeLevel = 0,
|
||
};
|
||
|
||
public static bool JogDial(string label, ref double delta, Vector2 size)
|
||
{
|
||
ImGui.PushStyleVar(ImGuiStyleVar.ButtonTextAlign, new Vector2(1, 0.5f));
|
||
var isActive = ImGui.Button(label + "###dummy", size);
|
||
ImGui.PopStyleVar();
|
||
var io = ImGui.GetIO();
|
||
if (ImGui.IsItemActive())
|
||
{
|
||
var center = (ImGui.GetItemRectMin() + ImGui.GetItemRectMax()) * 0.5f;
|
||
ImGui.SetMouseCursor(ImGuiMouseCursor.Hand);
|
||
ImGui.GetForegroundDrawList().AddCircle(center, 100, UiColors.Gray, 50);
|
||
isActive = true;
|
||
|
||
var pLast = io.MousePos - io.MouseDelta - center;
|
||
var pNow = io.MousePos - center;
|
||
var aLast = Math.Atan2(pLast.X, pLast.Y);
|
||
var aNow = Math.Atan2(pNow.X, pNow.Y);
|
||
delta = aLast - aNow;
|
||
if (delta > 1.5)
|
||
{
|
||
delta -= 2 * Math.PI;
|
||
}
|
||
else if (delta < -1.5)
|
||
{
|
||
delta += 2 * Math.PI;
|
||
}
|
||
}
|
||
|
||
return isActive;
|
||
}
|
||
} |