using ImGuiNET;
using T3.Core.DataTypes.Vector;
using T3.Core.Utils;
using T3.Editor.Gui.InputUi.VectorInputs;
using T3.Editor.Gui.UiHelpers;
using T3.Editor.Gui.OutputUi;
using T3.Editor.Gui.Styling;
using T3.Editor.UiModel;
using T3.Editor.UiModel.ProjectHandling;
using T3.Editor.UiModel.Selection;
using Vector2 = System.Numerics.Vector2;
using Vector4 = System.Numerics.Vector4;
namespace T3.Editor.Gui.Windows.Exploration;
///
/// Renders the
///
internal sealed class ExplorationWindow : Window
{
public ExplorationWindow()
{
_variationCanvas = new ExploreVariationCanvas(this);
Config.Title = "Variation Explorer";
Config.Visible = true;
}
private Guid _compositionSymbolId;
private bool CheckFavoriteMatchesNodeSelection(ExplorationVariation variation, NodeSelection nodeSelection)
{
var match = true;
foreach (var param in variation.ValuesForParameters.Keys)
{
if (!nodeSelection.GetSelectedChildUis().Contains(param.TargetChild))
{
match = false;
}
}
return match;
}
protected override void DrawContent()
{
ImGui.BeginChild("params", new Vector2(200, -1));
{
ImGui.PushStyleVar(ImGuiStyleVar.ItemInnerSpacing, new Vector2(4,4));
var currentGraphCanvas = ProjectView.Focused;
if (currentGraphCanvas != null)
{
DrawSidePanelContent(currentGraphCanvas.NodeSelection, currentGraphCanvas.Structure);
}
ImGuiUtils.ResetCursorForExtentCheck();
ImGui.PopStyleVar();
ImGui.EndChild();
}
ImGui.SameLine();
ImGui.BeginChild("canvas", new Vector2(-1, -1), ImGuiChildFlags.None, ImGuiWindowFlags.NoScrollWithMouse | ImGuiWindowFlags.NoScrollbar);
{
_variationCanvas.Draw(ProjectView.Focused?.Structure);
}
ImGui.EndChild();
}
private static float _strengthBeforeDrag = 0;
private void DrawSidePanelContent(NodeSelection nodeSelection, Structure structure)
{
// List selected operators and parameters
ImGui.DragFloat("Scatter", ref _variationCanvas.Scatter, 0.1f, 0, 100);
_compositionSymbolId = nodeSelection.GetSelectionSymbolChildId() ?? Guid.Empty;
var selectedSymbolUiChilds = nodeSelection.GetSelectedChildUis();
// Remove no longer selected parameters
var symbolChildUis = selectedSymbolUiChilds as SymbolUi.Child[] ?? selectedSymbolUiChilds.ToArray();
for (var index = VariationParameters.Count - 1; index >= 0; index--)
{
if (!symbolChildUis.Contains(VariationParameters[index].TargetChild))
{
VariationParameters.RemoveAt(index);
}
}
foreach (var symbolChildUi in symbolChildUis)
{
ImGui.Indent(5);
ImGui.PushFont(Fonts.FontBold);
ImGui.Selectable(symbolChildUi.SymbolChild.ReadableName);
ImGui.PopFont();
ImGui.PushStyleColor(ImGuiCol.Text, UiColors.Gray.Rgba);
ImGui.PushID(symbolChildUi.Id.GetHashCode());
var keepX = ImGui.GetCursorPosX();
foreach (var input in symbolChildUi.SymbolChild.Inputs.Values)
{
ImGui.PushID(input.Id.GetHashCode());
var p = input.DefaultValue;
// TODO: check if input is connected
if (p.ValueType == typeof(float)
|| p.ValueType == typeof(Vector2)
|| p.ValueType == typeof(Vector3)
|| p.ValueType == typeof(Vector4)
)
{
var matchingParam =
VariationParameters.FirstOrDefault(variationParam =>
input == variationParam.Input && symbolChildUi.Id == variationParam.TargetChild.Id);
var selected = matchingParam != null;
if (matchingParam != null)
{
var keep = ImGui.GetCursorPos();
var formattedStrength = $"×{matchingParam.ScatterStrength:F1}";
var size = ImGui.CalcTextSize(formattedStrength);
ImGui.SetCursorPosX(ImGui.GetContentRegionAvail().X- size.X - 5);
ImGui.TextUnformatted(formattedStrength);
ImGui.SetCursorPos(keep);
ImGui.SetCursorPosX(ImGui.GetContentRegionAvail().X- 50);
ImGui.InvisibleButton("ScatterStrengthFactor", new Vector2(50, ImGui.GetTextLineHeight()));
if (ImGui.IsItemHovered() || ImGui.IsItemActive())
{
ImGui.SetMouseCursor(ImGuiMouseCursor.ResizeEW);
}
if (ImGui.IsItemActivated())
{
_strengthBeforeDrag = matchingParam.ScatterStrength;
}
if (ImGui.IsItemActive() && ImGui.IsMouseDragging(ImGuiMouseButton.Left))
{
matchingParam.ScatterStrength = (_strengthBeforeDrag + ImGui.GetMouseDragDelta().X * 0.02f).Clamp(0, 100f);
}
if (ImGui.IsItemDeactivated())
{
_variationCanvas.ClearVariations();
}
ImGui.SameLine();
ImGui.SetCursorPosX(keepX);
}
if (ImGui.Selectable(input.Name, selected))
{
if (selected)
{
VariationParameters.Remove(matchingParam);
}
else
{
var instance = nodeSelection.GetInstanceForChildUi(symbolChildUi);
var inputSlot = instance.Inputs.Single(input2 => input2.Id == input.Id);
//var xxx = symbolChildUi.SymbolChild.Symbol
var scale = 1f;
var min = float.NegativeInfinity;
var max = float.PositiveInfinity;
var clampMin = false;
var clampMax = false;
var symbolUi = symbolChildUi.SymbolChild.Symbol.GetSymbolUi();
var inputUi = symbolUi.InputUis[input.Id];
switch (inputUi)
{
case FloatInputUi floatInputUi:
scale = floatInputUi.Scale;
min = floatInputUi.Min;
max = floatInputUi.Max;
clampMin = floatInputUi.ClampMin;
clampMax = floatInputUi.ClampMax;
break;
case Vector2InputUi float2InputUi:
scale = float2InputUi.Scale;
min = float2InputUi.Min;
max = float2InputUi.Max;
clampMin = float2InputUi.ClampMin;
clampMax = float2InputUi.ClampMax;
break;
case Vector3InputUi float3InputUi:
scale = float3InputUi.Scale;
min = float3InputUi.Min;
max = float3InputUi.Max;
clampMin = float3InputUi.ClampMin;
clampMax = float3InputUi.ClampMax;
break;
case Vector4InputUi float4InputUi:
scale = 0.02f; // Reasonable default for color variations
break;
}
VariationParameters.Add(new ExplorationVariation.VariationParameter()
{
TargetChild = symbolChildUi,
Input = input,
InstanceIdPath = instance.InstancePath,
Type = p.ValueType,
InputSlot = inputSlot,
ParameterScale = scale,
ParameterMin = min,
ParameterMax = max,
ParameterClampMin = clampMin,
ParameterClampMax = clampMax,
});
}
_variationCanvas.ClearVariations();
}
}
ImGui.PopID();
}
ImGui.PopID();
ImGui.Dummy(Spacing);
ImGui.PopStyleColor();
ImGui.Unindent(5);
}
// List Snapshots
if (_compositionSymbolId != Guid.Empty && _variationsForSymbols.TryGetValue(_compositionSymbolId, out var savedForComposition))
{
ImGui.Separator();
// Header
ImGui.PushFont(Fonts.FontBold);
var itemWidth = ImGui.GetContentRegionAvail().X - 16;
ImGui.TextUnformatted("Snapshots");
ImGui.PopFont();
ImGui.SameLine(itemWidth);
if (CustomComponents.IconButton(Icon.Trash, new Vector2(16, 16)))
{
Log.Debug("Not implemented");
}
CustomComponents.TooltipForLastItem("Remove not liked snapshots");
ExplorationVariation deleteAfterIteration = null;
foreach (var variation in savedForComposition)
{
var isMatching = CheckFavoriteMatchesNodeSelection(variation, nodeSelection);
ImGui.PushStyleColor(ImGuiCol.Text, isMatching ? UiColors.Gray.Rgba : NonMatchingVarationsColor);
ImGui.PushID(variation.GetHashCode());
{
var isSelected = _blendedVariations.Contains(variation);
if (CustomComponents.IconButton(isSelected ? Icon.ChevronRight : Icon.Pin, new Vector2(16, 16)))
{
if (isSelected)
{
_blendedVariations.Remove(variation);
}
else
{
_blendedVariations.Add(variation);
}
LayoutBlendedVariations();
}
ImGui.SameLine();
if (ImGui.Selectable(variation.Title, false, 0, new Vector2(ImGui.GetWindowWidth() - 32, 0)))
{
SelectVariation(variation, nodeSelection, structure);
}
if (ImGui.IsItemHovered())
{
if (_lastHoveredVariation == null)
{
variation.KeepCurrentAndApplyNewValues(structure);
_lastHoveredVariation = variation;
}
else if (_lastHoveredVariation != variation)
{
_lastHoveredVariation.RestoreValues();
variation.KeepCurrentAndApplyNewValues(structure);
_lastHoveredVariation = variation;
}
// Hover relevant operators
foreach (var param in _lastHoveredVariation.ValuesForParameters.Keys)
{
FrameStats.AddHoveredId(param.TargetChild.Id);
}
}
else
{
var wasHoveredBefore = _lastHoveredVariation == variation;
if (wasHoveredBefore)
{
_lastHoveredVariation.RestoreValues();
_lastHoveredVariation = null;
}
}
ImGui.SameLine();
// Delete button
if (CustomComponents.IconButton(variation.IsLiked ? Icon.Heart : Icon.HeartOutlined, new Vector2(16, 16)))
{
variation.IsLiked = !variation.IsLiked;
//deleteAfterIteration = variation;
}
}
ImGui.PopStyleColor();
ImGui.PopID();
}
if (deleteAfterIteration != null)
{
savedForComposition.Remove(deleteAfterIteration);
}
}
}
private void LayoutBlendedVariations()
{
_variationCanvas.ClearVariations();
_variationCanvas.ResetView();
// Merge parameter list
var parameters = new List();
foreach (var variation in _blendedVariations)
{
foreach (var param in variation.ValuesForParameters.Keys)
{
if (!parameters.Contains(param))
parameters.Add(param);
}
}
const int steps = 7;
if (_blendedVariations.Count == 2)
{
for (int stepY = 0; stepY <= steps; stepY++)
{
var ty = (float)stepY / steps;
var inputVariationsAndWeights = new List>()
{
new(_blendedVariations[0], 1 - ty),
new(_blendedVariations[1], ty),
};
var newVariation = ExplorationVariation.Mix(parameters, inputVariationsAndWeights, 0, GridCell.Center + new GridCell(0, stepY - steps / 2));
_variationCanvas.AddVariationToGrid(newVariation);
}
}
else if (_blendedVariations.Count == 3)
{
for (int stepY = 0; stepY <= steps; stepY++)
{
for (int stepX = 0; stepX <= steps; stepX++)
{
var tx = (float)stepX / steps;
var ty = (float)stepY / steps;
var inputVariationsAndWeights = new List>()
{
new(_blendedVariations[0], (1 - tx) * (1 - ty)),
new(_blendedVariations[1], (1 - tx) * (ty)),
new(_blendedVariations[2], (tx) * (ty)),
};
var gridCell = GridCell.Center + new GridCell(stepX - steps / 2, stepY - steps / 2);
var newVariation = ExplorationVariation.Mix(parameters, inputVariationsAndWeights, 0, gridCell);
_variationCanvas.AddVariationToGrid(newVariation);
}
}
}
}
private void SelectVariation(ExplorationVariation variation, NodeSelection nodeSelection, Structure structure)
{
variation.ApplyPermanently();
variation.UpdateUndoCommand(structure);
// Select variation
nodeSelection.Clear();
VariationParameters.Clear();
var alreadyAdded = new HashSet();
foreach (var param in variation.ValuesForParameters.Keys.Distinct())
{
VariationParameters.Add(param);
if (!alreadyAdded.Contains(param.TargetChild))
{
nodeSelection.AddSelection(param.TargetChild, structure.GetInstanceFromIdPath(param.InstanceIdPath));
alreadyAdded.Add(param.TargetChild);
}
}
_variationCanvas.ClearVariations();
}
private List _blendedVariations = new();
internal override List GetInstances()
{
return new List();
}
public void SaveVariation(ExplorationVariation variation)
{
_savedVariationIndex++;
variation.Title = GetRandomTitle();
if (_variationsForSymbols.TryGetValue(_compositionSymbolId, out var list))
{
list.Add(variation);
}
else
{
_variationsForSymbols[_compositionSymbolId] = new List { variation };
}
}
private string GetRandomTitle()
{
return RandomNames[_random.Next(RandomNames.Length)] + " " + RandomNames[_random.Next(RandomNames.Length)];
}
private static Random _random = new();
private static string[] RandomNames =
{
"Ace", "Age", "Ego", "Aid", "Aim", "Air", "Ape", "Barf", "Ass", "Axe", "Bad", "Big", "Boa", "Bro", "Bug", "Bum", "Cat", "Cow", "Cult", "Dog",
"Duck", "Eel", "Egg", "Eye", "Funk", "Fix", "Fox", "Fun", "Gut", "Hack", "Freak", "Cyber", "Dope", "Hip", "Bit",
"Mega", "Bomb", "Hot", "Jump", "Cult", "Ice", "Mad", "Mix", "Mud", "Off", "Ohm", "Oil", "One", "Two", "Four", "Five", "Six", "Pet", "Pig",
"Poo", "Pop", "Pot", "Pub", "Raw", "Red", "Green", "Pink", "Black", "White", "Orange", "Fine", "Fog", "Sad", "Sea", "Sex", "Shy", "Sin", "Sir",
"Ska", "Toy", "Tea", "Retro", "Monkey", "Top", "Wet", "Zoo"
};
public IOutputUi OutputUi { get; set; }
private readonly Dictionary> _variationsForSymbols = new();
private ExplorationVariation _lastHoveredVariation;
private readonly ExploreVariationCanvas _variationCanvas;
private static readonly Vector2 Spacing = new(1, 5);
private static readonly Color NonMatchingVarationsColor = new(0.3f);
private static int _savedVariationIndex = 1;
internal readonly List VariationParameters = new();
}