tooll3--t3
382 行
12 KiB
C#
382 行
12 KiB
C#
#nullable enable
|
|
|
|
using ImGuiNET;
|
|
using T3.Core.Utils;
|
|
using T3.Editor.Gui;
|
|
using T3.Editor.Gui.Input;
|
|
using T3.Editor.Gui.Styling;
|
|
using T3.Editor.Gui.UiHelpers;
|
|
using T3.Editor.Skills.Data;
|
|
using T3.Editor.UiModel.Selection;
|
|
|
|
namespace T3.Editor.Skills.Ui;
|
|
|
|
internal static class SkillMapEditor
|
|
{
|
|
internal static void ShowNextFrame()
|
|
{
|
|
_isOpen = true;
|
|
}
|
|
|
|
internal static void Draw()
|
|
{
|
|
if (!_isOpen)
|
|
return;
|
|
|
|
ImGui.PushStyleVar(ImGuiStyleVar.WindowBorderSize, 1);
|
|
ImGui.SetNextWindowSize(new Vector2(500, 500) * T3Ui.UiScaleFactor, ImGuiCond.Once);
|
|
if (ImGui.Begin("Edit skill map", ref _isOpen))
|
|
{
|
|
// ImGui.BeginChild("LevelList", new Vector2(120 * T3Ui.UiScaleFactor, 0));
|
|
// {
|
|
// ImGui.Indent(10);
|
|
//
|
|
// for (var index = 0; index < SkillMapData.Data.Topics.Count; index++)
|
|
// {
|
|
// var t = SkillMapData.Data.Topics[index];
|
|
// ImGui.PushID(index);
|
|
//
|
|
// if (ImGui.Selectable($"{t.Title}", _selectedTopics.Contains(t)))
|
|
// {
|
|
// _selectedTopics.Clear();
|
|
// _selectedTopics.Add(t);
|
|
// }
|
|
//
|
|
// ImGui.PopID();
|
|
// }
|
|
//
|
|
// ImGui.Unindent(10);
|
|
// FormInputs.AddVerticalSpace();
|
|
// }
|
|
// ImGui.EndChild();
|
|
//
|
|
// ImGui.SameLine();
|
|
ImGui.BeginChild("Inner", new Vector2(-200, 0), ImGuiChildFlags.None, ImGuiWindowFlags.NoMove);
|
|
{
|
|
ImGui.SameLine();
|
|
|
|
if (ImGui.Button("Save"))
|
|
{
|
|
SkillMapData.Save();
|
|
}
|
|
|
|
if (ImGui.IsWindowAppearing())
|
|
{
|
|
if (_selectedTopics.Count == 0)
|
|
{
|
|
var tmp = new HashSet<QuestTopic>(SkillMapData.Data.Topics);
|
|
_canvas.FocusTopics(tmp);
|
|
}
|
|
}
|
|
|
|
DrawInteractiveMap();
|
|
}
|
|
|
|
ImGui.EndChild();
|
|
ImGui.SameLine();
|
|
ImGui.BeginChild("SidePanel", new Vector2(200, 0));
|
|
{
|
|
DrawSidebar();
|
|
}
|
|
ImGui.EndChild();
|
|
}
|
|
|
|
ImGui.End();
|
|
ImGui.PopStyleVar();
|
|
}
|
|
|
|
private static void DrawInteractiveMap()
|
|
{
|
|
var isAnyItemHovered = _canvas.DrawContent(HandleTopicInteraction2, out var mouseCell, _selectedTopics);
|
|
|
|
if (_state == States.DraggingItems)
|
|
{
|
|
if (_draggedTopic == null || ImGui.IsMouseReleased(ImGuiMouseButton.Left))
|
|
{
|
|
_draggedTopic = null;
|
|
_state = States.Default;
|
|
}
|
|
else
|
|
{
|
|
var draggedCell = _draggedTopic.Cell;
|
|
var dX = mouseCell.X - draggedCell.X;
|
|
var dY = mouseCell.Y - draggedCell.Y;
|
|
|
|
var movedSomewhat = dX != 0 || dY != 0;
|
|
|
|
if (movedSomewhat)
|
|
{
|
|
var moveCellDelta = new HexCanvas.Cell(dX, dY);
|
|
var isBlocked = false;
|
|
foreach (var t in _selectedTopics)
|
|
{
|
|
var newCell = t.Cell + moveCellDelta;
|
|
|
|
if (_blockedCellIds.Contains(newCell.GetHashCode()))
|
|
{
|
|
isBlocked = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!isBlocked)
|
|
{
|
|
foreach (var t in _selectedTopics)
|
|
{
|
|
t.Cell += moveCellDelta;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
var dl = ImGui.GetWindowDrawList();
|
|
if (!isAnyItemHovered && ImGui.IsWindowHovered() && !ImGui.IsMouseDown(ImGuiMouseButton.Right))
|
|
{
|
|
HandleFenceSelection();
|
|
if (_fence.State != SelectionFence.States.Updated)
|
|
DrawHoveredEmptyCell(dl, mouseCell);
|
|
}
|
|
}
|
|
|
|
private static void HandleFenceSelection()
|
|
{
|
|
var shouldBeActive =
|
|
ImGui.IsWindowHovered(ImGuiHoveredFlags.AllowWhenBlockedByPopup)
|
|
&& _state == States.Default;
|
|
|
|
if (!shouldBeActive)
|
|
{
|
|
_fence.Reset();
|
|
return;
|
|
}
|
|
|
|
switch (_fence.UpdateAndDraw(out var selectMode))
|
|
{
|
|
case SelectionFence.States.PressedButNotMoved:
|
|
if (selectMode == SelectionFence.SelectModes.Replace)
|
|
_selectedTopics.Clear();
|
|
break;
|
|
|
|
case SelectionFence.States.Updated:
|
|
HandleSelectionFenceUpdate(_fence.BoundsUnclamped, selectMode);
|
|
|
|
break;
|
|
|
|
case SelectionFence.States.CompletedAsClick:
|
|
// A hack to prevent clearing selection when opening parameter popup
|
|
if (ImGui.IsPopupOpen("", ImGuiPopupFlags.AnyPopup))
|
|
break;
|
|
|
|
_selectedTopics.Clear();
|
|
break;
|
|
case SelectionFence.States.Inactive:
|
|
break;
|
|
case SelectionFence.States.CompletedAsArea:
|
|
break;
|
|
default:
|
|
throw new ArgumentOutOfRangeException();
|
|
}
|
|
}
|
|
|
|
private static void HandleSelectionFenceUpdate(ImRect bounds, SelectionFence.SelectModes selectMode)
|
|
{
|
|
if (selectMode == SelectionFence.SelectModes.Replace)
|
|
{
|
|
_selectedTopics.Clear();
|
|
}
|
|
|
|
foreach (var topic in SkillMapData.Data.Topics)
|
|
{
|
|
var centerOnScreen = _canvas.ScreenPosFromCell(topic.Cell);
|
|
if (!bounds.Contains(centerOnScreen))
|
|
continue;
|
|
|
|
if (selectMode == SelectionFence.SelectModes.Remove)
|
|
{
|
|
_selectedTopics.Remove(topic);
|
|
}
|
|
else
|
|
{
|
|
_selectedTopics.Add(topic);
|
|
}
|
|
}
|
|
}
|
|
|
|
private static void DrawSidebar()
|
|
{
|
|
if (_selectedTopics.Count != 1)
|
|
return;
|
|
|
|
var topic = _selectedTopics.First();
|
|
|
|
if (ImGui.IsKeyDown(ImGuiKey.A) && !ImGui.IsAnyItemActive())
|
|
{
|
|
_state = States.LinkingItems;
|
|
}
|
|
|
|
var isSelectingUnlocked = _state == States.LinkingItems;
|
|
|
|
if (CustomComponents.ToggleIconButton(ref isSelectingUnlocked, Icon.ConnectedOutput, Vector2.Zero))
|
|
{
|
|
_state = isSelectingUnlocked ? States.LinkingItems : States.Default;
|
|
}
|
|
|
|
ImGui.Indent(5);
|
|
var autoFocus = false;
|
|
if (_focusTopicNameInput)
|
|
{
|
|
autoFocus = true;
|
|
_focusTopicNameInput = false;
|
|
}
|
|
|
|
FormInputs.DrawFieldSetHeader("Topic");
|
|
ImGui.PushID(topic.Id.GetHashCode());
|
|
FormInputs.AddStringInput("##Topic", ref topic.Title, autoFocus: autoFocus);
|
|
FormInputs.AddVerticalSpace();
|
|
|
|
if (FormInputs.AddEnumDropdown(ref topic.TopicType, "##Type"))
|
|
{
|
|
}
|
|
|
|
FormInputs.DrawFieldSetHeader("Requires");
|
|
if (FormInputs.AddEnumDropdown(ref topic.Requirement, "##Requirements"))
|
|
{
|
|
}
|
|
|
|
|
|
FormInputs.DrawFieldSetHeader("Namespace");
|
|
FormInputs.AddStringInput("##NameSpace", ref topic.Namespace);
|
|
|
|
FormInputs.DrawFieldSetHeader("Description");
|
|
topic.Description ??= string.Empty;
|
|
CustomComponents.DrawMultilineTextEdit(ref topic.Description);
|
|
|
|
ImGui.PopID();
|
|
}
|
|
|
|
private static void DrawHoveredEmptyCell(ImDrawListPtr dl, HexCanvas.Cell cell)
|
|
{
|
|
var hoverCenter = _canvas.ScreenPosFromCell(cell);
|
|
_dampedHoverCanvasPos = MathUtils.Lerp(_dampedHoverCanvasPos, hoverCenter, 0.5f);
|
|
|
|
dl.AddNgonRotated(_dampedHoverCanvasPos, _canvas.HexRadiusOnScreen, UiColors.ForegroundFull.Fade(0.1f), false);
|
|
|
|
var activeTopic = _selectedTopics.Count == 0 ? null : _selectedTopics.First();
|
|
|
|
if (ImGui.IsMouseDoubleClicked(ImGuiMouseButton.Left))
|
|
{
|
|
var newTopic = new QuestTopic
|
|
{
|
|
Id = Guid.NewGuid(),
|
|
MapCoordinate = new Vector2(cell.X, cell.Y),
|
|
Title = "New topic" + SkillMapData.Data.Topics.Count(),
|
|
ZoneId = activeTopic?.ZoneId ?? Guid.Empty,
|
|
TopicType = _lastType,
|
|
ProgressionState = activeTopic?.ProgressionState ?? QuestTopic.ProgressStates.Locked,
|
|
Requirement = activeTopic?.Requirement ?? QuestTopic.Requirements.AllInputPaths,
|
|
};
|
|
|
|
_selectedTopics.Clear();
|
|
_selectedTopics.Add(newTopic);
|
|
_focusTopicNameInput = true;
|
|
}
|
|
else if (ImGui.IsMouseClicked(ImGuiMouseButton.Left))
|
|
{
|
|
_selectedTopics.Clear();
|
|
}
|
|
}
|
|
|
|
private static void HandleTopicInteraction2(QuestTopic topic, bool isSelected)
|
|
{
|
|
switch (_state)
|
|
{
|
|
case States.Default:
|
|
if (ImGui.IsMouseClicked(ImGuiMouseButton.Left))
|
|
_state = States.HoldingItem;
|
|
|
|
break;
|
|
|
|
case States.HoldingItem:
|
|
if (ImGui.IsMouseReleased(ImGuiMouseButton.Left))
|
|
{
|
|
if (!ImGui.GetIO().KeyShift)
|
|
{
|
|
_selectedTopics.Clear();
|
|
}
|
|
|
|
_selectedTopics.Add(topic);
|
|
_lastType = topic.TopicType;
|
|
}
|
|
|
|
// Start Dragging
|
|
if (ImGui.IsMouseDragging(ImGuiMouseButton.Left))
|
|
{
|
|
if (!_selectedTopics.Contains(topic))
|
|
{
|
|
_selectedTopics.Clear();
|
|
_selectedTopics.Add(topic);
|
|
}
|
|
|
|
_state = States.DraggingItems;
|
|
_canvas.InverseTransformPositionFloat(ImGui.GetMousePos());
|
|
_draggedTopic = topic;
|
|
|
|
// Initialize blocked cells to avoid collisions
|
|
_blockedCellIds.Clear();
|
|
foreach (var t in SkillMapData.Data.Topics)
|
|
{
|
|
if (_selectedTopics.Contains(t))
|
|
continue;
|
|
|
|
_blockedCellIds.Add(new HexCanvas.Cell(t.MapCoordinate).GetHashCode());
|
|
}
|
|
}
|
|
|
|
break;
|
|
|
|
case States.LinkingItems:
|
|
if (_selectedTopics.Count != 1 || isSelected)
|
|
{
|
|
_state = States.Default;
|
|
break;
|
|
}
|
|
|
|
if (ImGui.IsMouseClicked(ImGuiMouseButton.Left))
|
|
{
|
|
var activeTopic = _selectedTopics.First();
|
|
if (!activeTopic.UnlocksTopics.Remove(topic.Id))
|
|
{
|
|
activeTopic.UnlocksTopics.Add(topic.Id);
|
|
}
|
|
|
|
if (!ImGui.GetIO().KeyShift)
|
|
{
|
|
_state = States.Default;
|
|
}
|
|
}
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
private enum States
|
|
{
|
|
Default,
|
|
HoldingItem,
|
|
LinkingItems,
|
|
DraggingItems,
|
|
}
|
|
|
|
private static States _state;
|
|
|
|
private static Vector2 _dampedHoverCanvasPos;
|
|
private static readonly HashSet<int> _blockedCellIds = new(64);
|
|
private static QuestTopic? _draggedTopic;
|
|
private static bool _isOpen;
|
|
private static readonly HashSet<QuestTopic> _selectedTopics = new();
|
|
private static bool _focusTopicNameInput;
|
|
private static QuestTopic.TopicTypes _lastType = QuestTopic.TopicTypes.Numbers;
|
|
private static readonly SelectionFence _fence = new();
|
|
private static readonly SkillMapCanvas _canvas = new();
|
|
} |