using ImGuiNET; using Newtonsoft.Json; using Newtonsoft.Json.Linq; using T3.Core.Animation; using T3.Core.Operator; using T3.Core.Operator.Slots; using T3.Editor.Gui.Input; using T3.Editor.Gui.Styling; using T3.Editor.UiModel.InputsAndTypes; using T3.Serialization; namespace T3.Editor.Gui.InputUi.VectorInputs; internal abstract class FloatVectorInputValueUi : InputValueUi { protected FloatVectorInputValueUi(int componentCount) { FloatComponents = new float[componentCount]; } public override bool IsAnimatable => true; protected FloatVectorInputValueUi CloneWithType() where TT : FloatVectorInputValueUi, new() { return new TT() { Max = Max, Min = Min, _scale = _scale, ClampMin = ClampMin, ClampMax = ClampMax, InputDefinition = InputDefinition, Parent = Parent, PosOnCanvas = PosOnCanvas, Relevancy = Relevancy, Size = Size, }; } protected override InputEditStateFlags DrawAnimatedValue(string name, InputSlot inputSlot, Animator animator) { var time = Playback.Current.TimeInBars; if (!animator.TryGetCurvesForInputSlot(inputSlot, out var curves) || curves.Length < FloatComponents.Length) { ImGui.PushID(inputSlot.Parent.SymbolChildId.GetHashCode() + inputSlot.Id.GetHashCode()); DrawReadOnlyControl(name, ref inputSlot.Value); ImGui.PopID(); return InputEditStateFlags.Nothing; } for (var index = 0; index < FloatComponents.Length; index++) { FloatComponents[index] = (float)curves[index].GetSampledValue(time); } ImGui.PushID(inputSlot.Parent.SymbolChildId.GetHashCode() + inputSlot.Id.GetHashCode()); var inputEditState = DrawEditControl(name, inputSlot.Input, ref inputSlot.Value, false); ImGui.PopID(); if ((inputEditState & InputEditStateFlags.Modified) == InputEditStateFlags.Modified) { inputSlot.SetTypedInputValue(inputSlot.Value); } return inputEditState; } protected override void DrawReadOnlyControl(string name, ref T float2Value) { ImGui.PushStyleColor(ImGuiCol.Text, UiColors.StatusAutomated.Rgba); DrawEditControl(name, null!, ref float2Value, true); ImGui.PopStyleColor(); } protected override string GetSlotValueAsString(ref T float2Value) { return string.Format(T3Ui.FloatNumberFormat, float2Value); } public abstract override void ApplyValueToAnimation(IInputSlot inputSlot, InputValue inputValue, Animator animator, double time); private struct ValueSettingPreset { public string Title; public float Scale; public float MinValue; public float MaxValue; public string CustomFormat; public bool ClampRangeMin; public bool ClampRangeMax; public Vector2InputUi.Vec2Controls Vec2Control; public float? DefaultComponentValue; } private ValueSettingPreset[] _valueSettingPresets = { new() { Scale = 0, Title = "Rotation", MinValue = -180, MaxValue = 180, CustomFormat = "{0:0.0}°" }, new() { Scale = 0, Title = "Translation", MinValue = -2, MaxValue = 2, CustomFormat = null, Vec2Control = Vector2InputUi.Vec2Controls.Position, }, new() { Scale = 0, Title = "Color", MinValue = 0, MaxValue = 1, CustomFormat = null, ClampRangeMin = true, }, new() { Scale = 0, Title = "Signed Scale × Factor", MinValue = -5, MaxValue = 5, CustomFormat = "{0:0.000} ×", }, new() { Scale = 0, Title = "Scale × Factor", MinValue = 0, MaxValue = 5, CustomFormat = "{0:0.000} ×", }, new() { Scale = 0, Title = "Gain & Bias", MinValue = 0, MaxValue = 1, CustomFormat = "{0:0.000}", ClampRangeMin = true, ClampRangeMax = true, Vec2Control = Vector2InputUi.Vec2Controls.GainAndBias, DefaultComponentValue = 0.5f, }, }; public override bool DrawSettings() { var modified = false; var keepPosition = ImGui.GetCursorPos(); ImGui.SetCursorPos(new Vector2(ImGui.GetWindowSize().X - ImGui.GetFrameHeight() - ImGui.GetStyle().FramePadding.X, 0)); if (CustomComponents.IconButton(Icon.Settings2, Vector2.One * ImGui.GetFrameHeight())) { ImGui.OpenPopup("customFormats"); } ImGui.SetCursorPos(keepPosition); if (ImGui.BeginPopup("customFormats")) { ImGui.TextUnformatted("Apply formatting presets..."); foreach (var p in _valueSettingPresets) { if (!ImGui.Button(p.Title)) continue; Min = p.MinValue; Max = p.MaxValue; _scale = p.Scale; ClampMin = p.ClampRangeMin; ClampMax = p.ClampRangeMax; Format = p.CustomFormat; if (this is Vector2InputUi vector2InputUi) { vector2InputUi.UseVec2Control = p.Vec2Control; } if (p.DefaultComponentValue is { } defaultComponentValue) { ApplyDefaultComponentValue(defaultComponentValue); } Parent?.FlagAsModified(); ImGui.CloseCurrentPopup(); modified = true; } ImGui.EndPopup(); } modified |= base.DrawSettings(); FormInputs.DrawFieldSetHeader("Value Range"); if (FormInputs.DrawValueRangeControl(ref Min, ref Max, ref _scale, ref ClampMin, ref ClampMax, DefaultMin, DefaultMax, DefaultScale)) { modified = true; } FormInputs.DrawFieldSetHeader("Custom Value format"); if ( FormInputs.AddStringInput("##valueFormat", ref Format, "Custom format like {0:0.0}", null, "Defines custom value format. Here are some examples:\n\n" + "{0:0.00000} - High precision\n" + "{0:0}× - With a suffix\n" + "{0:G5} - scientific notation", null) ) { modified = true; if (string.IsNullOrWhiteSpace(Format)) { Format = null; } } return modified; } public override void Write(JsonTextWriter writer) { base.Write(writer); // ReSharper disable CompareOfFloatsByEqualityOperator if (Min != DefaultMin) writer.WriteValue("Min", Min); if (Max != DefaultMax) writer.WriteValue("Max", Max); if (_scale != DefaultScale) writer.WriteValue("Scale", _scale); if (ClampMin) writer.WriteValue("ClampMin", ClampMin); if (ClampMax) writer.WriteValue("ClampMax", ClampMax); if (!string.IsNullOrEmpty(Format)) writer.WriteObject("Format", Format); // ReSharper enable CompareOfFloatsByEqualityOperator } public void CopyTo(FloatVectorInputValueUi target) { target.Min = Min; target.Max = Max; target.Scale = Scale; target.ClampMin = ClampMin; target.ClampMax = ClampMax; target.Format = Format; } public override void Read(JToken inputToken) { base.Read(inputToken); if (inputToken == null) return; Min = inputToken["Min"]?.Value() ?? DefaultMin; Max = inputToken["Max"]?.Value() ?? DefaultMax; _scale = inputToken["Scale"]?.Value() ?? DefaultScale; var legacyClamp = inputToken["Clamp"]?.Value() ?? false; ClampMin = (inputToken["ClampMin"]?.Value() ?? false) | legacyClamp; ClampMax = (inputToken["ClampMax"]?.Value() ?? false) | legacyClamp; Format = inputToken["Format"]?.Value(); } private void ApplyDefaultComponentValue(float value) { switch (InputDefinition.DefaultValue) { case InputValue floatValue: floatValue.Value = value; break; case InputValue vector2Value: vector2Value.Value = new Vector2(value); break; case InputValue vector3Value: vector3Value.Value = new Vector3(value); break; case InputValue vector4Value: vector4Value.Value = new Vector4(value); break; default: return; } Parent?.Symbol.InvalidateInputDefaultInInstances(InputDefinition.Id); } private static float GetScaleFromRange(float scale, float min, float max) { // Automatically set scale from range if (scale > 0) return scale; // A lame hack because we can't set infinity values in parameter properties if (min < -9999 || max > 9999) { return 0.01f; } return Math.Abs(min - max) / 100; } public float Min = DefaultMin; public float Max = DefaultMax; private float _scale = DefaultScale; public float Scale { get => FloatInputUi.GetScaleFromRange(_scale, Min, Max); set => _scale = value; } //private bool Clamp; public bool ClampMin; public bool ClampMax; public string Format; protected readonly float[] FloatComponents; private const float DefaultScale = 0.0f; private const float DefaultMin = -9999999f; private const float DefaultMax = 9999999f; }