Vendor node editor in-tree; add material presets; debug drag regression

Move the imgui-node-editor subset the build actually compiles from
third_party/imgui-node-editor to src/ui/NodeEditor (version-controlled,
MIT LICENSE included). FindImguiNodeEditor.cmake points at the new
location; CMakeLists excludes src/ui/NodeEditor from UI_SOURCES so it
isn't compiled twice.

Material editor presets: a Presets menu (disabled with no open material)
builds a UsdPreviewSurface + UsdUVTexture graph fed by an st primvar
reader, or a MaterialX standard_surface + image graph fed by a texcoord
node. Each is one idempotent, undoable command that re-normalizes layout.
Create Material becomes an icon button. New nodes route through
FindFreeCanvasSpot so a creation never lands on top of an existing node
(overlapping nodes fight over the editor hit test and become undraggable).

Shader-ball preview: pick a previewable output (terminal or 3/4-component
color-like) instead of always the first output, so scalar-only nodes keep
the whole-material preview rather than failing Storm codegen. Per-shape
camera frame-fit margins and auto-clip framing.

Fixes: DeletePrimCommand::Undo recreates missing destination ancestors
before SdfCopySpec (parent material may have been deleted after the
command ran). ConfigWindowsMoveFromTitleBarOnly stops a content-area drag
in the node canvas from moving the whole Material Editor window.

Temporary (marked for removal once the node-editor drag regression is
diagnosed): main.cpp mirrors LOG_INFO to %APPDATA%\UsdLayerManager\
debug.log; a g_AxNodeEditorDebugLog hook in the vendored editor plus a
[NodeGraph] event-trace block in RenderNodeGraphCanvas dump click/drag/
selection/position-save state.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-07-08 22:35:23 +08:00
parent 0f30e080e3
commit 038450a1ca
29 changed files with 13658 additions and 27 deletions
+5
View File
@@ -173,6 +173,11 @@ endif()
# Collect source files
file(GLOB_RECURSE CORE_SOURCES "src/core/*.cpp")
file(GLOB_RECURSE UI_SOURCES "src/ui/*.cpp")
# src/ui/NodeEditor is the in-tree vendored imgui-node-editor subset — already
# compiled into the imgui_node_editor_impl target by FindImguiNodeEditor.cmake
# (with its own required include dirs/defines); excluded here so it isn't
# compiled a second time, incorrectly, as part of this target's own sources.
list(FILTER UI_SOURCES EXCLUDE REGEX "/src/ui/NodeEditor/")
file(GLOB_RECURSE UTILS_SOURCES "src/utils/*.cpp")
set(MAIN_SOURCE "src/main.cpp")
+11 -7
View File
@@ -1,9 +1,13 @@
# FindImguiNodeEditor.cmake - Find or configure thedmd/imgui-node-editor
#
# This module looks for imgui-node-editor in the third_party/imgui-node-editor
# directory (vendored, docking branch) and compiles its sources directly
# against this project's own Imgui::Imgui target so it shares the exact same
# ImGui headers/defines (no second copy of ImGui is pulled in).
# This module looks for imgui-node-editor at src/ui/NodeEditor (an in-tree,
# version-controlled subset of the upstream library — MIT licensed, see
# src/ui/NodeEditor/LICENSE — containing only the files this project's build
# actually compiles; the full upstream drop, including the standalone example
# app used to reproduce library-level bugs against a pure reference build,
# stays vendored separately at third_party/imgui-node-editor) and compiles its
# sources directly against this project's own Imgui::Imgui target so it shares
# the exact same ImGui headers/defines (no second copy of ImGui is pulled in).
#
# Inputs:
# IMGUI_NODE_EDITOR_DIR - Path to imgui-node-editor/NodeEditor root
@@ -13,12 +17,12 @@
# ImguiNodeEditor::ImguiNodeEditor (imported STATIC target)
if(NOT IMGUI_NODE_EDITOR_DIR)
set(IMGUI_NODE_EDITOR_DIR "${CMAKE_SOURCE_DIR}/third_party/imgui-node-editor/NodeEditor")
set(IMGUI_NODE_EDITOR_DIR "${CMAKE_SOURCE_DIR}/src/ui/NodeEditor")
endif()
if(NOT IMGUI_NODE_EDITOR_BLUEPRINT_UTILITIES_DIR)
set(IMGUI_NODE_EDITOR_BLUEPRINT_UTILITIES_DIR
"${CMAKE_SOURCE_DIR}/third_party/imgui-node-editor/Examples/Common/BlueprintUtilities")
"${CMAKE_SOURCE_DIR}/src/ui/NodeEditor/BlueprintWidgets")
endif()
find_path(ImguiNodeEditor_INCLUDE_DIR
@@ -48,7 +52,7 @@ endforeach()
include(FindPackageHandleStandardArgs)
find_package_handle_standard_args(ImguiNodeEditor
REQUIRED_VARS ImguiNodeEditor_INCLUDE_DIR
FAIL_MESSAGE "imgui-node-editor not found — expected it vendored at third_party/imgui-node-editor/NodeEditor"
FAIL_MESSAGE "imgui-node-editor not found — expected it at src/ui/NodeEditor"
)
if(ImguiNodeEditor_FOUND AND ImguiNodeEditor_SOURCES_MISSING)
+8
View File
@@ -59,6 +59,14 @@ void DeletePrimCommand::Undo() {
if (!rootLayer) return;
try {
// The destination ancestors may be gone by now (e.g. the parent
// material was itself deleted after this command ran) — SdfCopySpec
// fails with "No spec ... when trying to set field 'primChildren'"
// when the parent spec is missing, so recreate the chain first.
pxr::SdfPath parentPath = m_primPath.GetParentPath();
if (!parentPath.IsAbsoluteRootPath() && !rootLayer->GetPrimAtPath(parentPath))
pxr::SdfCreatePrimInLayer(rootLayer, parentPath);
if (!pxr::SdfCopySpec(m_savedLayer, m_primPath, rootLayer, m_primPath))
LOG_ERROR("DeletePrimCommand::Undo: SdfCopySpec failed for " + m_primPath.GetString());
} catch (const std::exception& e) {
+11 -1
View File
@@ -5,6 +5,7 @@
#include <exception>
#include <Windows.h>
#include <string>
#include <filesystem>
static void SetUsdPluginPath() {
char exePath[MAX_PATH];
@@ -98,7 +99,16 @@ static void SetUsdPluginPath() {
int main(int /*argc*/, char* /*argv*/[]) {
try {
UsdLayerManager::Logger::Instance().SetLogLevel(UsdLayerManager::LogLevel::Info);
// Temporary: capture LOG_INFO to a file so the [NodeGraph]/[ed] debug
// traces are readable after the fact (this is a WIN32-subsystem app,
// so stdout isn't visible when launched normally). Remove once the
// node-editor drag regression is diagnosed.
if (const char* appData = getenv("APPDATA")) {
std::filesystem::path logDir = std::filesystem::path(appData) / "UsdLayerManager";
std::filesystem::create_directories(logDir);
UsdLayerManager::Logger::Instance().SetLogFile((logDir / "debug.log").string());
}
LOG_INFO("=== USD Layer Manager Starting ===");
SetUsdPluginPath();
+10
View File
@@ -98,6 +98,16 @@ bool ImGuiContext::Initialize(const std::string& windowTitle, int width, int hei
// position overlay appears then); a plain drag just moves the window,
// so casual rearranging can't accidentally re-dock panels.
io.ConfigDockingWithShift = true;
// Windows move only via their title bar/tab, never by dragging empty
// content. Without this, a press-drag that a content widget doesn't
// capture falls through to ImGui's "drag body to move window" behaviour —
// which, in the node-editor canvas (whose background button doesn't
// reliably claim the drag in this nested-child embedding), meant dragging
// in the graph moved the whole Material Editor window. Title-bar dragging
// and Shift-to-dock are unaffected; this is also the desired behaviour for
// every other content area (viewports, timelines) where a content drag
// must never move the host window.
io.ConfigWindowsMoveFromTitleBarOnly = true;
// Store imgui.ini in %APPDATA%\UsdLayerManager\ so it doesn't litter the CWD.
if (const char* appData = getenv("APPDATA")) {
+450 -9
View File
@@ -26,6 +26,11 @@
#include <cctype>
#include <cstdio>
// UsdLayerManager local patch hook in the vendored node editor (see
// imgui_node_editor.cpp): when set, BuildControl logs its click-time
// hit-test state through it. Temporary debugging aid.
extern void (*g_AxNodeEditorDebugLog)(const char*);
namespace UsdLayerManager {
namespace NE = ax::NodeEditor;
@@ -221,6 +226,10 @@ void ApplyFallbackLayout(ShaderGraphSnapshot& graph) {
} // namespace
MaterialEditorPanel::MaterialEditorPanel() {
// Route the vendored editor's click-time hit-test dump into our log so
// it interleaves with the [NodeGraph] event trace.
g_AxNodeEditorDebugLog = [](const char* msg) { LOG_INFO(std::string(msg)); };
NE::Config config;
config.SettingsFile = nullptr; // layout persists as USD uiPosition custom data, not an on-disk .json
config.UserPointer = this;
@@ -303,12 +312,30 @@ void MaterialEditorPanel::RenderPreviewPanel() {
bool outputIsTerminal = false;
if (!m_selectedNodePath.IsEmpty()) {
for (const auto& node : m_graph.nodes) {
if (node.path == m_selectedNodePath && !node.outputs.empty()) {
previewNode = node.path;
previewOutput = node.outputs.front().name;
outputIsTerminal = (node.outputs.front().typeName == pxr::SdfValueTypeNames->Token);
break;
if (node.path != m_selectedNodePath)
continue;
// Pick a previewable output: a terminal (surface) or a
// 3/4-component value the wrapper can plug into diffuseColor.
// Scalar outputs (e.g. UsdUVTexture's Sdr-first output "r")
// make Storm's codegen swizzle .xyz off a float and fail to
// compile — nodes with only scalar outputs keep the
// whole-material preview instead.
const auto& tn = pxr::SdfValueTypeNames;
for (const auto& out : node.outputs) {
const pxr::SdfValueTypeName& t = out.typeName;
const bool terminal = (t == tn->Token);
const bool colorLike =
t == tn->Color3f || t == tn->Float3 || t == tn->Vector3f ||
t == tn->Normal3f || t == tn->Point3f ||
t == tn->Color4f || t == tn->Float4;
if (terminal || colorLike) {
previewNode = node.path;
previewOutput = out.name;
outputIsTerminal = terminal;
break;
}
}
break;
}
}
m_preview.SetMaterial(m_stage, m_materialPath, ComputeGraphRevision(m_stage, m_graph),
@@ -585,9 +612,47 @@ void MaterialEditorPanel::CommitInputEdit(const ShaderGraphNode& node, const Sha
}
void MaterialEditorPanel::RenderToolbar() {
if (ImGui::Button("Create Material"))
// Create Material icon button
bool createClicked;
if (m_iconManager) {
createClicked = ImGui::ImageButton("##createMaterial",
ImTextureRef(m_iconManager->Get(Icon::FilePlus)),
ImVec2(18.f, 18.f));
} else {
createClicked = ImGui::Button("Create Material");
}
if (ImGui::IsItemHovered())
ImGui::SetTooltip("Create Material");
if (createClicked)
CreateNewMaterial();
// Presets menu: entries appear per node source when the required shader
// definitions are registered, and need an open material to apply to.
ImGui::SameLine();
ImGui::BeginDisabled(m_materialPath.IsEmpty());
if (ImGui::Button("Presets"))
ImGui::OpenPopup("MaterialPresets");
ImGui::EndDisabled();
if (ImGui::BeginPopup("MaterialPresets")) {
auto& sdr = pxr::SdrRegistry::GetInstance();
if (ImGui::BeginMenu("USD")) {
ImGui::BeginDisabled(!sdr.GetShaderNodeByIdentifier(pxr::TfToken("UsdPreviewSurface")));
if (ImGui::MenuItem("Default Shader Graph"))
CreateUsdPresetGraph();
ImGui::EndDisabled();
ImGui::EndMenu();
}
if (ImGui::BeginMenu("MaterialX")) {
ImGui::BeginDisabled(
!sdr.GetShaderNodeByIdentifier(pxr::TfToken("ND_standard_surface_surfaceshader")));
if (ImGui::MenuItem("Standard Surface Graph"))
CreateMaterialXPresetGraph();
ImGui::EndDisabled();
ImGui::EndMenu();
}
ImGui::EndPopup();
}
if (!m_targetPrimPath.IsEmpty()) {
ImGui::SameLine();
ImGui::Text("Selected: %s", m_targetPrimPath.GetText());
@@ -731,6 +796,159 @@ void MaterialEditorPanel::CreateNewMaterial() {
OpenOrCreateMaterial(materialsScope.AppendChild(pxr::TfToken(finalName)).GetString());
}
namespace {
// Shared by the preset builders below.
pxr::UsdShadeShader DefinePresetShader(const pxr::UsdStageRefPtr& stage,
const pxr::SdfPath& materialPath,
const char* name, const char* shaderId,
const pxr::GfVec2f& uiPos) {
pxr::UsdShadeShader shader =
pxr::UsdShadeShader::Define(stage, materialPath.AppendChild(pxr::TfToken(name)));
shader.CreateIdAttr(pxr::VtValue(pxr::TfToken(shaderId)));
shader.GetPrim().SetCustomDataByKey(pxr::TfToken("uiPosition"), pxr::VtValue(uiPos));
return shader;
}
} // namespace
void MaterialEditorPanel::CreateUsdPresetGraph() {
if (!m_stage || !m_commandHistory || m_materialPath.IsEmpty()) return;
// Re-applying onto an existing preset graph is allowed: the build is
// idempotent (same prims/connections re-authored, user-set values like
// texture file paths survive) and re-normalizes the node layout.
pxr::UsdStageRefPtr stage = m_stage;
pxr::SdfPath matPath = m_materialPath;
auto build = [stage, matPath]() {
// Column/row spacing must exceed the rendered node sizes (full Sdr
// pin sets make these ~400px tall): overlapping nodes steal each
// other's clicks in the editor's hit test and become undraggable.
pxr::UsdShadeShader surf = DefinePresetShader(
stage, matPath, "UsdPreviewSurface", "UsdPreviewSurface", pxr::GfVec2f(0.f, 400.f));
pxr::UsdShadeShader st = DefinePresetShader(
stage, matPath, "stReader", "UsdPrimvarReader_float2", pxr::GfVec2f(-840.f, 600.f));
st.CreateInput(pxr::TfToken("varname"), pxr::SdfValueTypeNames->Token)
.Set(pxr::TfToken("st"));
struct TexPreset {
const char* name;
const char* texOutput; pxr::SdfValueTypeName outType;
const char* destInput; pxr::SdfValueTypeName destType;
bool rawColorSpace;
};
const TexPreset textures[] = {
{"diffuseTexture", "rgb", pxr::SdfValueTypeNames->Float3,
"diffuseColor", pxr::SdfValueTypeNames->Color3f, false},
{"metallicTexture", "r", pxr::SdfValueTypeNames->Float,
"metallic", pxr::SdfValueTypeNames->Float, true},
{"roughnessTexture", "r", pxr::SdfValueTypeNames->Float,
"roughness", pxr::SdfValueTypeNames->Float, true},
{"normalTexture", "rgb", pxr::SdfValueTypeNames->Float3,
"normal", pxr::SdfValueTypeNames->Normal3f, true},
};
float y = 0.f;
for (const TexPreset& t : textures) {
pxr::UsdShadeShader tex = DefinePresetShader(
stage, matPath, t.name, "UsdUVTexture", pxr::GfVec2f(-420.f, y));
y += 440.f;
tex.CreateInput(pxr::TfToken("st"), pxr::SdfValueTypeNames->Float2)
.ConnectToSource(st.ConnectableAPI(), pxr::TfToken("result"));
if (t.rawColorSpace)
tex.CreateInput(pxr::TfToken("sourceColorSpace"), pxr::SdfValueTypeNames->Token)
.Set(pxr::TfToken("raw"));
surf.CreateInput(pxr::TfToken(t.destInput), t.destType)
.ConnectToSource(tex.ConnectableAPI(), pxr::TfToken(t.texOutput));
}
// Normal maps need the [0,1] texture range remapped to [-1,1].
pxr::UsdShadeShader normalTex(
stage->GetPrimAtPath(matPath.AppendChild(pxr::TfToken("normalTexture"))));
normalTex.CreateInput(pxr::TfToken("scale"), pxr::SdfValueTypeNames->Float4)
.Set(pxr::GfVec4f(2.f, 2.f, 2.f, 1.f));
normalTex.CreateInput(pxr::TfToken("bias"), pxr::SdfValueTypeNames->Float4)
.Set(pxr::GfVec4f(-1.f, -1.f, -1.f, 0.f));
pxr::UsdShadeMaterial material(stage->GetPrimAtPath(matPath));
if (material)
material.CreateSurfaceOutput().ConnectToSource(
surf.ConnectableAPI(), pxr::TfToken("surface"));
};
auto remove = [stage, matPath]() {
for (const char* name : {"UsdPreviewSurface", "stReader", "diffuseTexture",
"metallicTexture", "roughnessTexture", "normalTexture"})
stage->RemovePrim(matPath.AppendChild(pxr::TfToken(name)));
if (pxr::UsdPrim mat = stage->GetPrimAtPath(matPath))
mat.RemoveProperty(pxr::TfToken("outputs:surface"));
};
m_commandHistory->Push(std::make_unique<AttributeSetCommand>(
"Create USD Preset Graph", build, remove));
// Force a position reseed: on a re-apply the nodes are already known to
// the editor at their old canvas positions.
m_nodeIdToPath.clear();
SyncFromUsd();
}
void MaterialEditorPanel::CreateMaterialXPresetGraph() {
if (!m_stage || !m_commandHistory || m_materialPath.IsEmpty()) return;
// Re-applying is allowed; see CreateUsdPresetGraph.
pxr::UsdStageRefPtr stage = m_stage;
pxr::SdfPath matPath = m_materialPath;
auto build = [stage, matPath]() {
// See the USD preset: spacing must exceed rendered node sizes or
// overlapping nodes steal each other's drag hit-tests.
pxr::UsdShadeShader surf = DefinePresetShader(
stage, matPath, "standard_surface", "ND_standard_surface_surfaceshader",
pxr::GfVec2f(0.f, 300.f));
pxr::UsdShadeShader texcoord = DefinePresetShader(
stage, matPath, "texcoord", "ND_texcoord_vector2", pxr::GfVec2f(-840.f, 400.f));
struct ImagePreset {
const char* name;
const char* shaderId; pxr::SdfValueTypeName outType;
const char* destInput; pxr::SdfValueTypeName destType;
};
const ImagePreset images[] = {
{"base_color_image", "ND_image_color3", pxr::SdfValueTypeNames->Color3f,
"base_color", pxr::SdfValueTypeNames->Color3f},
{"specular_roughness_image", "ND_image_float", pxr::SdfValueTypeNames->Float,
"specular_roughness", pxr::SdfValueTypeNames->Float},
{"metalness_image", "ND_image_float", pxr::SdfValueTypeNames->Float,
"metalness", pxr::SdfValueTypeNames->Float},
};
float y = 0.f;
for (const ImagePreset& img : images) {
pxr::UsdShadeShader tex = DefinePresetShader(
stage, matPath, img.name, img.shaderId, pxr::GfVec2f(-420.f, y));
y += 400.f;
tex.CreateInput(pxr::TfToken("texcoord"), pxr::SdfValueTypeNames->Float2)
.ConnectToSource(texcoord.ConnectableAPI(), pxr::TfToken("out"));
surf.CreateInput(pxr::TfToken(img.destInput), img.destType)
.ConnectToSource(tex.ConnectableAPI(), pxr::TfToken("out"));
}
// MaterialX networks terminate on the mtlx render-context output
// (matches usdMtlx-imported materials).
pxr::UsdShadeMaterial material(stage->GetPrimAtPath(matPath));
if (material)
material.CreateSurfaceOutput(pxr::TfToken("mtlx")).ConnectToSource(
surf.ConnectableAPI(), pxr::TfToken("out"));
};
auto remove = [stage, matPath]() {
for (const char* name : {"standard_surface", "texcoord", "base_color_image",
"specular_roughness_image", "metalness_image"})
stage->RemovePrim(matPath.AppendChild(pxr::TfToken(name)));
if (pxr::UsdPrim mat = stage->GetPrimAtPath(matPath))
mat.RemoveProperty(pxr::TfToken("outputs:mtlx:surface"));
};
m_commandHistory->Push(std::make_unique<AttributeSetCommand>(
"Create MaterialX Preset Graph", build, remove));
m_nodeIdToPath.clear(); // reseed positions (see CreateUsdPresetGraph)
SyncFromUsd();
}
void MaterialEditorPanel::OpenOrCreateMaterial(const std::string& pathStr) {
if (!m_stage) return;
if (!pxr::SdfPath::IsValidPathString(pathStr, nullptr)) {
@@ -883,6 +1101,8 @@ void MaterialEditorPanel::RenderNodeGraphCanvas() {
NE::Begin("MaterialEditorCanvas", ImVec2(0.0f, 0.0f));
if (!m_pendingCreateShaderId.empty()) {
// Browser-list creations land at the view center; CreateShaderNode
// nudges the spot clear of existing nodes.
CreateShaderNode(m_pendingCreateShaderId, NE::ScreenToCanvas(viewCenterScreen));
m_pendingCreateShaderId.clear();
}
@@ -1001,6 +1221,164 @@ void MaterialEditorPanel::RenderNodeGraphCanvas() {
NE::End();
// --- Debug tracing of node interaction events. Edge-triggered so each
// event logs once, not per frame. Event order in the editor is:
// LMB press -> node becomes active (and is brought to front) -> drag
// moves it -> release -> "click" -> selection updates -> position save.
{
// Logs the mouse position and every node's editor rect with a hit
// verdict, and returns the hit paths. Topmost is unknown to us, but
// the full table makes overlap fights directly visible in the log.
// hitIds, when given, collects the editor ids of the hit nodes.
auto nodeRectsAtMouse = [this](std::vector<uintptr_t>* hitIds) {
std::string hits;
const ImVec2 screen = ImGui::GetMousePos();
const ImVec2 mouse = NE::ScreenToCanvas(screen);
char buf[512];
std::snprintf(buf, sizeof(buf),
"[NodeGraph] hit test: mouse screen (%.1f, %.1f) canvas (%.1f, %.1f), %zu node(s)",
screen.x, screen.y, mouse.x, mouse.y, m_graph.nodes.size());
LOG_INFO(std::string(buf));
for (const auto& node : m_graph.nodes) {
NE::NodeId nodeId(HashId(node.path.GetString()));
const ImVec2 pos = NE::GetNodePosition(nodeId);
const ImVec2 size = NE::GetNodeSize(nodeId);
if (pos.x == FLT_MAX || size.x <= 0.0f) {
LOG_INFO("[NodeGraph] " + node.path.GetString() +
": no editor rect yet (not rendered)");
continue;
}
const bool hit = mouse.x >= pos.x && mouse.x <= pos.x + size.x &&
mouse.y >= pos.y && mouse.y <= pos.y + size.y;
std::snprintf(buf, sizeof(buf),
"[NodeGraph] %s: rect min (%.1f, %.1f) size (%.1f x %.1f)%s",
node.path.GetText(), pos.x, pos.y, size.x, size.y,
hit ? " <-- HIT" : "");
LOG_INFO(std::string(buf));
if (hit) {
hits += (hits.empty() ? "" : ", ") + node.path.GetString();
if (hitIds)
hitIds->push_back(nodeId.Get());
}
}
return hits;
};
// Editor NodeIds of the currently selected nodes.
auto selectedNodeIds = []() {
std::vector<uintptr_t> ids;
const int count = NE::GetSelectedObjectCount();
if (count > 0) {
std::vector<NE::NodeId> sel(static_cast<size_t>(count));
const int nodeCount = NE::GetSelectedNodes(sel.data(), count);
ids.reserve(static_cast<size_t>(nodeCount));
for (int i = 0; i < nodeCount; ++i)
ids.push_back(sel[static_cast<size_t>(i)].Get());
}
return ids;
};
auto nodeNames = [this](const std::vector<uintptr_t>& ids) {
std::string names;
for (uintptr_t idValue : ids) {
auto it = m_nodeIdToPath.find(idValue);
names += (names.empty() ? "" : ", ");
names += (it != m_nodeIdToPath.end()) ? it->second.GetName() : "<unknown>";
}
return names;
};
auto containsAnyOf = [](const std::vector<uintptr_t>& haystack,
const std::vector<uintptr_t>& needles) {
for (uintptr_t needle : needles)
if (std::find(haystack.begin(), haystack.end(), needle) != haystack.end())
return true;
return false;
};
if (canvasHovered && ImGui::IsMouseClicked(ImGuiMouseButton_Left)) {
m_debugDragNodeIds.clear();
const std::string hits = nodeRectsAtMouse(&m_debugDragNodeIds);
m_debugPressOnNode = !hits.empty();
m_debugDragLogged = false;
if (m_debugPressOnNode) {
// Pressing an already-selected node keeps the selection (no
// clear, no rect-select) so a drag moves the whole group —
// the editor only updates selection on release.
const std::vector<uintptr_t> selected = selectedNodeIds();
std::string msg = "[NodeGraph] LMB press over node rect(s): " + hits;
if (selected.size() > 1 && containsAnyOf(selected, m_debugDragNodeIds))
msg += " [in current selection of " + std::to_string(selected.size()) +
" -> drag moves all selected]";
LOG_INFO(msg);
} else {
LOG_INFO("[NodeGraph] LMB press on empty canvas (rect select / clear selection on release)");
}
}
if (m_debugPressOnNode && !m_debugDragLogged &&
ImGui::IsMouseDragging(ImGuiMouseButton_Left)) {
m_debugDragLogged = true;
// Mirror DragAction::Accept: dragging a selected node moves the
// whole selection, so trace every group member from here on.
const std::vector<uintptr_t> selected = selectedNodeIds();
if (selected.size() > 1 && containsAnyOf(selected, m_debugDragNodeIds)) {
m_debugDragNodeIds = selected;
LOG_INFO("[NodeGraph] drag start: group move of " +
std::to_string(selected.size()) + " selected node(s): " +
nodeNames(m_debugDragNodeIds));
} else {
LOG_INFO("[NodeGraph] drag start (ready to move), cursor over: " + nodeRectsAtMouse(nullptr));
}
}
// Per-move trace while a drag is live, only on frames the mouse
// actually moved: mouse position, accumulated drag delta, and the
// live editor position of every tracked node — the whole selection
// for a group drag, else the node(s) hit at press time (a node that
// stops tracking the delta shows up immediately).
if (m_debugDragLogged && ImGui::IsMouseDragging(ImGuiMouseButton_Left) &&
(io.MouseDelta.x != 0.0f || io.MouseDelta.y != 0.0f)) {
const ImVec2 screen = ImGui::GetMousePos();
const ImVec2 canvas = NE::ScreenToCanvas(screen);
const ImVec2 delta = ImGui::GetMouseDragDelta(ImGuiMouseButton_Left, 0.0f);
char buf[512];
std::snprintf(buf, sizeof(buf),
"[NodeGraph] drag move: mouse screen (%.1f, %.1f) canvas (%.1f, %.1f) dragDelta (%.1f, %.1f)",
screen.x, screen.y, canvas.x, canvas.y, delta.x, delta.y);
std::string msg = buf;
for (uintptr_t idValue : m_debugDragNodeIds) {
const ImVec2 nodePos = NE::GetNodePosition(NE::NodeId(idValue));
auto it = m_nodeIdToPath.find(idValue);
std::snprintf(buf, sizeof(buf), "; %s at (%.1f, %.1f)",
it != m_nodeIdToPath.end() ? it->second.GetName().c_str() : "<unknown>",
nodePos.x, nodePos.y);
msg += buf;
}
LOG_INFO(msg);
}
if (ImGui::IsMouseReleased(ImGuiMouseButton_Left) &&
(m_debugPressOnNode || m_debugDragLogged)) {
LOG_INFO(std::string("[NodeGraph] LMB release") +
(m_debugDragLogged ? " (ends drag; position save should follow)" : " (click, no drag)"));
m_debugPressOnNode = false;
m_debugDragLogged = false;
m_debugDragNodeIds.clear();
}
// Covers single-click selection and rubber-band rect multi-select
// alike; fires on the frame the editor's selection list changes.
if (NE::HasSelectionChanged())
{
const int count = NE::GetSelectedObjectCount();
std::vector<NE::NodeId> selected(static_cast<size_t>(std::max(count, 1)));
const int nodeCount = NE::GetSelectedNodes(selected.data(), count);
std::string msg = "[NodeGraph] selection changed: " + std::to_string(nodeCount) + " node(s)";
for (int i = 0; i < nodeCount; ++i) {
auto it = m_nodeIdToPath.find(selected[static_cast<size_t>(i)].Get());
msg += (i == 0 ? ": " : ", ");
msg += (it != m_nodeIdToPath.end()) ? it->second.GetString() : "<unknown>";
}
LOG_INFO(msg);
}
}
// Mirror the editor's node selection (first selected node) for the
// properties section under the preview.
m_selectedNodePath = pxr::SdfPath();
@@ -1124,6 +1502,54 @@ void MaterialEditorPanel::RenderNodeSearchMenu(const ImVec2& canvasPos) {
}
}
ImVec2 MaterialEditorPanel::FindFreeCanvasSpot(ImVec2 desired, const std::string& shaderId) const {
// Estimated footprint of the not-yet-rendered node: same height model as
// ApplyFallbackLayout, width covering typical icon+label pin rows.
auto estimateSize = [](size_t pinCount) {
return ImVec2(340.0f, static_cast<float>(pinCount) * 24.0f + 80.0f);
};
size_t newPinCount = 6;
if (pxr::SdrShaderNodeConstPtr sdrNode =
pxr::SdrRegistry::GetInstance().GetShaderNodeByIdentifier(pxr::TfToken(shaderId)))
newPinCount = sdrNode->GetShaderInputNames().size() + sdrNode->GetShaderOutputNames().size();
const ImVec2 newSize = estimateSize(newPinCount);
struct Rect { ImVec2 pos, size; };
std::vector<Rect> rects;
rects.reserve(m_graph.nodes.size());
for (const auto& node : m_graph.nodes) {
Rect r{ImVec2(node.uiPosition[0], node.uiPosition[1]),
estimateSize(node.inputs.size() + node.outputs.size())};
// Nodes the editor has already rendered report their real rectangle.
NE::NodeId nodeId(HashId(node.path.GetString()));
const ImVec2 livePos = NE::GetNodePosition(nodeId);
const ImVec2 liveSize = NE::GetNodeSize(nodeId);
if (livePos.x != FLT_MAX && liveSize.x > 0.0f) {
r.pos = livePos;
r.size = liveSize;
}
rects.push_back(r);
}
const float margin = 24.0f;
bool moved = true;
while (moved) {
moved = false;
for (const Rect& r : rects) {
const bool overlaps =
desired.x < r.pos.x + r.size.x + margin && r.pos.x < desired.x + newSize.x + margin &&
desired.y < r.pos.y + r.size.y + margin && r.pos.y < desired.y + newSize.y + margin;
if (overlaps) {
// March right past the blocker; x only grows, so the scan
// terminates once it clears the rightmost overlapping node.
desired.x = r.pos.x + r.size.x + margin;
moved = true;
}
}
}
return desired;
}
void MaterialEditorPanel::CreateShaderNode(const std::string& shaderId, const ImVec2& canvasPos) {
if (!m_stage || !m_commandHistory || m_materialPath.IsEmpty()) return;
@@ -1136,7 +1562,8 @@ void MaterialEditorPanel::CreateShaderNode(const std::string& shaderId, const Im
finalName = baseName + "_" + std::to_string(++suffix);
pxr::SdfPath path = m_materialPath.AppendChild(pxr::TfToken(finalName));
pxr::GfVec2f pos(canvasPos.x, canvasPos.y);
const ImVec2 freePos = FindFreeCanvasSpot(canvasPos, shaderId);
pxr::GfVec2f pos(freePos.x, freePos.y);
m_commandHistory->Push(std::make_unique<CreateShaderNodeCommand>(m_stage, path, shaderId, pos));
SyncFromUsd();
@@ -1183,7 +1610,11 @@ void MaterialEditorPanel::PersistNodePosition(NE::NodeId nodeId) {
// auto-layout). Only a real drag diverges from it and should author.
for (const auto& node : m_graph.nodes) {
if (node.path == path) {
if (node.uiPosition == newValue) return;
if (node.uiPosition == newValue) {
LOG_INFO("[NodeGraph] position save for " + path.GetString() +
" ignored (echo of snapshot seed)");
return;
}
break;
}
}
@@ -1193,7 +1624,17 @@ void MaterialEditorPanel::PersistNodePosition(NE::NodeId nodeId) {
? oldValueVt.UncheckedGet<pxr::GfVec2f>()
: pxr::GfVec2f(0.0f, 0.0f);
if (oldValue == newValue) return; // e.g. redundant Save call right after our own seed
if (oldValue == newValue) {
LOG_INFO("[NodeGraph] position save for " + path.GetString() +
" ignored (customData already matches)");
return;
}
char moveMsg[256];
std::snprintf(moveMsg, sizeof(moveMsg),
"[NodeGraph] move committed: %s (%.1f, %.1f) -> (%.1f, %.1f)",
path.GetText(), oldValue[0], oldValue[1], newValue[0], newValue[1]);
LOG_INFO(std::string(moveMsg));
pxr::UsdStageRefPtr stage = m_stage;
m_commandHistory->Push(std::make_unique<AttributeSetCommand>(
+20
View File
@@ -12,6 +12,7 @@
#include <memory>
#include <string>
#include <unordered_map>
#include <vector>
namespace UsdLayerManager {
@@ -82,6 +83,11 @@ private:
void RenderMaterialBrowser();
/// Creates a uniquely-named empty material under /Materials and opens it.
void CreateNewMaterial();
/// Preset graphs for the open material (one undoable command each):
/// UsdPreviewSurface + UsdUVTextures fed by an st primvar reader, or a
/// MaterialX standard_surface + image nodes fed by a texcoord node.
void CreateUsdPresetGraph();
void CreateMaterialXPresetGraph();
void RenderNodeGraphCanvas();
void RenderPreviewPanel();
void HandleCreateAndDelete();
@@ -89,6 +95,11 @@ private:
/// creates the highlighted node at canvasPos.
void RenderNodeSearchMenu(const ImVec2& canvasPos);
void CreateShaderNode(const std::string& shaderId, const ImVec2& canvasPos);
/// Nudges a creation point right until the new node's estimated rectangle
/// clears every existing node's rendered rectangle. Overlapping nodes
/// fight over the editor's hit test: the buried one becomes unmovable and
/// its pins unhoverable. Must be called with the editor context current.
ImVec2 FindFreeCanvasSpot(ImVec2 desired, const std::string& shaderId) const;
/// Binds an existing material to targetPath, undoably, restoring whatever
/// direct binding (if any) targetPath had before.
void BindMaterialToTarget(const pxr::SdfPath& materialPath, const pxr::SdfPath& targetPath);
@@ -153,6 +164,15 @@ private:
/// is only valid inside the editor's Begin/End).
std::string m_pendingCreateShaderId;
/// Debug event-trace state (see the logging block at the end of
/// RenderNodeGraphCanvas): whether the last LMB press landed on a node
/// rect, whether that press's drag-start has been logged yet, and the
/// editor NodeIds hit at press time (their live positions are logged on
/// every drag-move so a node that stops following the mouse is visible).
bool m_debugPressOnNode = false;
bool m_debugDragLogged = false;
std::vector<uintptr_t> m_debugDragNodeIds;
/// First node currently selected in the graph editor (empty = none);
/// drives the properties section under the preview.
pxr::SdfPath m_selectedNodePath;
+17 -10
View File
@@ -37,17 +37,21 @@ const pxr::SdfPath kDomeLightPath("/Preview/DomeLight");
/// Selectable preview geometry; prim name is a child of kShapesRootPath.
/// Shapes backed by an asset file are only created when the file exists.
/// frameFit: camera framing margin — the camera frames the shape's bounding
/// box, and a cube fills its box completely while a sphere only inscribes it,
/// so boxy shapes need a larger margin to read at a similar visual size.
struct ShapePreset {
const char* label;
const char* primName;
double frameFit;
};
const ShapePreset kShapePresets[] = {
{"Sphere", "Sphere"},
{"Cube", "Cube"},
{"Cylinder", "Cylinder"},
{"Teapot", "Teapot"},
{"Hair", "Hair"},
{"Cloud Volume", "Cloud"},
{"Sphere", "Sphere", 1.6},
{"Cube", "Cube", 2.1},
{"Cylinder", "Cylinder", 1.8},
{"Teapot", "Teapot", 1.6},
{"Hair", "Hair", 1.6},
{"Cloud Volume", "Cloud", 1.6},
};
constexpr int kShapePresetCount = static_cast<int>(sizeof(kShapePresets) / sizeof(kShapePresets[0]));
@@ -155,7 +159,8 @@ void MaterialPreviewRenderer::EnsureInitialized() {
// ~25° down instead of the dead-on front view (subsequent shape switches
// keep whatever orientation the user has orbited to).
m_camera.Tumble(-45.0, 25.0);
m_renderer.SetCameraStateFromGfCamera(m_camera.ComputeGfCamera(m_previewBBox));
m_renderer.SetCameraStateFromGfCamera(
m_camera.ComputeGfCamera(m_previewBBox, /*autoClip=*/true));
m_initialized = true;
m_dirty = true;
@@ -307,8 +312,9 @@ void MaterialPreviewRenderer::ApplyPreviewShape(int index) {
// Shapes differ wildly in size (the cloud VDB spans hundreds of units) —
// reframe on the active shape's bounds; inactive prims don't contribute.
m_previewBBox = pxr::GfBBox3d(m_renderer.ComputeStageBounds());
m_camera.FrameSelection(m_previewBBox, 1.6); // margin so the shape doesn't touch the frame edge
m_renderer.SetCameraStateFromGfCamera(m_camera.ComputeGfCamera(m_previewBBox));
m_camera.FrameSelection(m_previewBBox, kShapePresets[index].frameFit);
m_renderer.SetCameraStateFromGfCamera(
m_camera.ComputeGfCamera(m_previewBBox, /*autoClip=*/true));
m_dirty = true;
}
@@ -402,7 +408,8 @@ uint32_t MaterialPreviewRenderer::Render(int width, int height) {
void MaterialPreviewRenderer::OrbitDrag(float deltaX, float deltaY) {
if (!m_initialized) return;
m_camera.Tumble(deltaX * 0.5, deltaY * 0.5); // matches the viewport's tumble sensitivity
m_renderer.SetCameraStateFromGfCamera(m_camera.ComputeGfCamera(m_previewBBox));
m_renderer.SetCameraStateFromGfCamera(
m_camera.ComputeGfCamera(m_previewBBox, /*autoClip=*/true));
m_dirty = true;
}
@@ -0,0 +1,12 @@
# pragma once
#include "imgui.h"
namespace ax {
namespace Drawing {
enum class IconType { Flow, Circle, Square, Grid, RoundSquare, Diamond };
void DrawIcon(ImDrawList* drawList, const ImVec2& a, const ImVec2& b, IconType type, bool filled, ImU32 color, ImU32 innerColor);
} // namespace Drawing
} // namespace ax
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,642 @@
//------------------------------------------------------------------------------
// Math 2D
//------------------------------------------------------------------------------
# if !defined(__AX_MATH_2D_INL__)
# define __AX_MATH_2D_INL__
# pragma once
//------------------------------------------------------------------------------
# if defined(__cplusplus)
//------------------------------------------------------------------------------
# include <utility>
# include <cmath>
//------------------------------------------------------------------------------
template <typename T, typename P>
inline ax::basic_point<T> operator + (const ax::basic_size<P>& lhs, const ax::basic_point<T>& rhs)
{
return static_cast<ax::basic_point<T>>(lhs) + rhs;
}
template <typename T, typename P>
inline ax::basic_point<T> operator + (const ax::basic_point<T>& lhs, const ax::basic_size<P>& rhs)
{
return lhs + static_cast<ax::basic_point<T>>(rhs);
}
//------------------------------------------------------------------------------
template <typename T>
inline typename ax::basic_rect<T>::point_t ax::basic_rect<T>::get_closest_point(const point_t& p, bool on_edge, float radius) const
{
auto point = get_closest_point(p, on_edge);
const auto offset = p - point;
const auto distance_sq = offset.x * offset.x + offset.y * offset.y;
if (distance_sq <= 0)
return point;
const auto distance = sqrtf(static_cast<float>(distance_sq));
return point + static_cast<basic_point<T>>(std::min(distance, static_cast<float>(radius)) * static_cast<basic_point<float>>(offset) * (1.0f / distance));
}
template <typename T>
inline typename ax::basic_rect<T>::point_t ax::basic_rect<T>::get_closest_point(const basic_rect& r) const
{
point_t result;
if (r.left() >= right())
result.x = right();
else if (r.right() <= left())
result.x = left();
else
result.x = (std::max(left(), r.left()) + std::min(right(), r.right())) / 2;
if (r.top() >= bottom())
result.y = bottom();
else if (r.bottom() <= top())
result.y = top();
else
result.y = (std::max(top(), r.top()) + std::min(bottom(), r.bottom())) / 2;
return result;
}
template <typename T>
inline typename ax::basic_rect<T>::point_t ax::basic_rect<T>::get_closest_point_hollow(const point_t& p, T rounding, rect_region* region) const
{
rounding = std::min(rounding, std::min(w, h) / 2);
const auto inner = expanded(-rounding);
if (p.x <= inner.left() && p.y <= inner.top())
{
if (region) *region = rect_region::top_left;
return inner.top_left().followed(p + point_t(-1, -1), rounding);
}
else if (p.x >= inner.right() && p.y <= inner.top())
{
if (region) *region = rect_region::top_right;
return inner.top_right().followed(p + point_t(1, -1), rounding);
}
else if (p.x <= inner.left() && p.y >= inner.bottom())
{
if (region) *region = rect_region::bottom_left;
return inner.bottom_left().followed(p + point_t(-1, 1), rounding);
}
else if (p.x >= inner.right() && p.y >= inner.bottom())
{
if (region) *region = rect_region::bottom_right;
return inner.bottom_right().followed(p + point_t(1, 1), rounding);
}
else
{
if (contains(p) || region)
{
const auto l_diff = abs(p.x - left());
const auto r_diff = abs(p.x - right());
const auto t_diff = abs(p.y - top());
const auto b_diff = abs(p.y - bottom());
if (l_diff <= r_diff && l_diff <= t_diff && l_diff <= b_diff)
{
if (region) *region = rect_region::left;
return point_t(left(), p.y);
}
else if (r_diff <= l_diff && r_diff <= t_diff && r_diff <= b_diff)
{
if (region) *region = rect_region::right;
return point_t(right(), p.y);
}
else if (t_diff <= l_diff && t_diff <= r_diff && t_diff <= b_diff)
{
if (region) *region = rect_region::top;
return point_t(p.x, top());
}
else
{
if (region) *region = rect_region::bottom;
return point_t(p.x, bottom());
}
}
else
return get_closest_point(p, true);
}
}
template <typename T>
inline ax::basic_line<T> ax::basic_rect<T>::get_closest_line(const basic_rect& r) const
{
auto a = get_closest_point(r);
auto b = r.get_closest_point(*this);
auto distribute = [](T& a, T& b, T a0, T a1, T b0, T b1)
{
if (a0 >= b1 || a1 <= b0)
return;
const auto aw = a1 - a0;
const auto bw = b1 - b0;
if (aw > bw)
{
b = b0 + bw - bw * (a - a0) / aw;
a = b;
}
else if (aw < bw)
{
a = a0 + aw - aw * (b - b0) / bw;
b = a;
}
};
distribute(a.x, b.x, left(), right(), r.left(), r.right());
distribute(a.y, b.y, top(), bottom(), r.top(), r.bottom());
return basic_line<T>(a, b);
}
template <typename T>
inline ax::basic_line<T> ax::basic_rect<T>::get_closest_line(const basic_rect& r, T radius) const
{
return get_closest_line(r, radius, radius);
}
template <typename T>
inline ax::basic_line<T> ax::basic_rect<T>::get_closest_line(const basic_rect& r, T radius_a, T radius_b) const
{
auto line = get_closest_line(r);
if (radius_a < 0)
radius_a = 0;
if (radius_b < 0)
radius_b = 0;
if (radius_a == 0 && radius_b == 0)
return line;
const auto offset = line.b - line.a;
const auto length_sq = offset.x * offset.x + offset.y * offset.y;
const auto radius_a_sq = radius_a * radius_a;
const auto radius_b_sq = radius_b * radius_b;
if (length_sq <= 0)
return line;
const auto length = sqrtf(static_cast<float>(length_sq));
const auto direction = ax::basic_point<float>(offset.x / length, offset.y / length);
const auto total_radius_sq = radius_a_sq + radius_b_sq;
float radius_a_f = static_cast<float>(radius_a);
float radius_b_f = static_cast<float>(radius_b);
if (total_radius_sq > length_sq)
{
const auto scale = length / (radius_a_f + radius_b_f);
radius_a_f *= scale;
radius_b_f *= scale;
}
line.a = line.a + static_cast<point_t>(direction * radius_a_f);
line.b = line.b - static_cast<point_t>(direction * radius_b_f);
return line;
}
//------------------------------------------------------------------------------
inline void ax::matrix::zero()
{
*this = matrix(0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f);
}
inline void ax::matrix::reset()
{
*this = matrix();
}
inline bool ax::matrix::invert()
{
const float det = (m11 * m22 - m21 * m12);
if (det == 0.0f)
return false;
const float invDet = 1.0f / det;
*this = matrix(
m22 * invDet,
-m12 * invDet,
-m21 * invDet,
m11 * invDet,
-(-m21 * m32 + m22 * m31) * invDet,
-( m11 * m32 - m12 * m31) * invDet);
return true;
}
inline void ax::matrix::translate(float x, float y, matrix_order order/* = matrix_order::prepend*/)
{
combine(matrix(1.0f, 0.0f, 0.0f, 1.0f, x, y), order);
}
inline void ax::matrix::rotate(float angle, matrix_order order/* = matrix_order::prepend*/)
{
float angleRad = angle * AX_PI / 180.0f;
const float c = cosf(angleRad);
const float s = sinf(angleRad);
combine(matrix(c, s, -s, c, 0.0f, 0.0f), order);
}
inline void ax::matrix::rotate_at(float angle, float cx, float cy, matrix_order order/* = matrix_order::prepend*/)
{
const auto angleRad = angle * AX_PI / 180.0f;
const auto c = cosf(angleRad);
const auto s = sinf(angleRad);
combine(matrix(c, s, -s, c,
-cx * c - cy * -s + cx,
-cx * s - cy * c + cy), order);
}
inline void ax::matrix::scale(float x, float y, matrix_order order/* = matrix_order::prepend*/)
{
combine(matrix(x, 0.0f, 0.0f, y, 0.0f, 0.0f), order);
}
inline void ax::matrix::shear(float x, float y, matrix_order order/* = matrix_order::prepend*/)
{
combine(matrix(1.0f, y, x, 1.0f, 0.0f, 0.0f), order);
}
inline void ax::matrix::combine(const matrix& matrix, matrix_order order/* = matrix_order::prepend*/)
{
if (order == matrix_order::set)
{
if (this != &matrix)
*this = matrix;
return;
}
const auto* am = this;
const auto* bm = &matrix;
if (order == matrix_order::append)
{
using std::swap;
swap(am, bm);
}
*this = ax::matrix(
am->m11 * bm->m11 + am->m21 * bm->m12,
bm->m11 * am->m12 + bm->m12 * am->m22,
bm->m21 * am->m11 + bm->m22 * am->m21,
am->m12 * bm->m21 + am->m22 * bm->m22,
bm->m31 * am->m11 + bm->m32 * am->m21 + am->m31,
bm->m31 * am->m12 + bm->m32 * am->m22 + am->m32);
}
inline ax::matrix ax::matrix::inverted() const
{
matrix inverted = *this;
inverted.invert();
return inverted;
}
//------------------------------------------------------------------------------
inline void ax::matrix4::zero()
{
*this = matrix4(
0.0f, 0.0f, 0.0f, 0.0f,
0.0f, 0.0f, 0.0f, 0.0f,
0.0f, 0.0f, 0.0f, 0.0f,
0.0f, 0.0f, 0.0f, 0.0f);
}
inline void ax::matrix4::reset()
{
*this = matrix4();
}
inline bool ax::matrix4::invert()
{
# if 0
// wild magic 4 inverse - http://www.geometrictools.com/Documentation/LaplaceExpansionTheorem.pdf
// 84 multiplications
// 66 adds/subs
// 1 division
// 24
const float fA0 = m11 * m22 - m12 * m21;
const float fA1 = m11 * m23 - m13 * m21;
const float fA2 = m11 * m24 - m14 * m21;
const float fA3 = m12 * m23 - m13 * m22;
const float fA4 = m12 * m24 - m14 * m22;
const float fA5 = m13 * m24 - m14 * m23;
const float fB0 = m31 * m42 - m32 * m41;
const float fB1 = m31 * m43 - m33 * m41;
const float fB2 = m31 * m44 - m34 * m41;
const float fB3 = m32 * m43 - m33 * m42;
const float fB4 = m32 * m44 - m34 * m42;
const float fB5 = m33 * m44 - m34 * m43;
// 6
const float det = fA0 * fB5 - fA1 * fB4 + fA2 * fB3 + fA3 * fB2 - fA4 * fB1 + fA5 * fB0;
if (det == 0.0f)
{
zero();
return false;
}
const float invDet = 1.0f / det;
// 36 + 16
*this = Matrix4(
( m22 * fB5 - m23 * fB4 + m24 * fB3) * invDet,
(-m12 * fB5 + m13 * fB4 - m14 * fB3) * invDet,
( m42 * fA5 - m43 * fA4 + m44 * fA3) * invDet,
(-m32 * fA5 + m33 * fA4 - m34 * fA3) * invDet,
(-m21 * fB5 + m23 * fB2 - m24 * fB1) * invDet,
( m11 * fB5 - m13 * fB2 + m14 * fB1) * invDet,
(-m41 * fA5 + m43 * fA2 - m44 * fA1) * invDet,
( m31 * fA5 - m33 * fA2 + m34 * fA1) * invDet,
( m21 * fB4 - m22 * fB2 + m24 * fB0) * invDet,
(-m11 * fB4 + m12 * fB2 - m14 * fB0) * invDet,
( m41 * fA4 - m42 * fA2 + m44 * fA0) * invDet,
(-m31 * fA4 + m32 * fA2 - m34 * fA0) * invDet,
(-m21 * fB3 + m22 * fB1 - m23 * fB0) * invDet,
( m11 * fB3 - m12 * fB1 + m13 * fB0) * invDet,
(-m41 * fA3 + m42 * fA1 - m43 * fA0) * invDet,
( m31 * fA3 - m32 * fA1 + m33 * fA0) * invDet);
return true;
# else
// 214 multiplications
// 80 adds/subs
// 1 division
const float d00 = m22 * m33 * m44 + m32 * m43 * m24 + m42 * m23 * m34 - m24 * m33 * m42 - m34 * m43 * m22 - m44 * m23 * m32;
const float d01 = m12 * m33 * m44 + m32 * m43 * m14 + m42 * m13 * m34 - m14 * m33 * m42 - m34 * m43 * m12 - m44 * m13 * m32;
const float d02 = m12 * m23 * m44 + m22 * m43 * m14 + m42 * m13 * m24 - m14 * m23 * m42 - m24 * m43 * m12 - m44 * m13 * m22;
const float d03 = m12 * m23 * m34 + m22 * m33 * m14 + m32 * m13 * m24 - m14 * m23 * m32 - m24 * m33 * m12 - m34 * m13 * m22;
const float d10 = m21 * m33 * m44 + m31 * m43 * m24 + m41 * m23 * m34 - m24 * m33 * m41 - m34 * m43 * m21 - m44 * m23 * m31;
const float d11 = m11 * m33 * m44 + m31 * m43 * m14 + m41 * m13 * m34 - m14 * m33 * m41 - m34 * m43 * m11 - m44 * m13 * m31;
const float d12 = m11 * m23 * m44 + m21 * m43 * m14 + m41 * m13 * m24 - m14 * m23 * m41 - m24 * m43 * m11 - m44 * m13 * m21;
const float d13 = m11 * m23 * m34 + m21 * m33 * m14 + m31 * m13 * m24 - m14 * m23 * m31 - m24 * m33 * m11 - m34 * m13 * m21;
const float d20 = m21 * m32 * m44 + m31 * m42 * m24 + m41 * m22 * m34 - m24 * m32 * m41 - m34 * m42 * m21 - m44 * m22 * m31;
const float d21 = m11 * m32 * m44 + m31 * m42 * m14 + m41 * m12 * m34 - m14 * m32 * m41 - m34 * m42 * m11 - m44 * m12 * m31;
const float d22 = m11 * m22 * m44 + m21 * m42 * m14 + m41 * m12 * m24 - m14 * m22 * m41 - m24 * m42 * m11 - m44 * m12 * m21;
const float d23 = m11 * m22 * m34 + m21 * m32 * m14 + m31 * m12 * m24 - m14 * m22 * m31 - m24 * m32 * m11 - m34 * m12 * m21;
const float d30 = m21 * m32 * m43 + m31 * m42 * m23 + m41 * m22 * m33 - m23 * m32 * m41 - m33 * m42 * m21 - m43 * m22 * m31;
const float d31 = m11 * m32 * m43 + m31 * m42 * m13 + m41 * m12 * m33 - m13 * m32 * m41 - m33 * m42 * m11 - m43 * m12 * m31;
const float d32 = m11 * m22 * m43 + m21 * m42 * m13 + m41 * m12 * m23 - m13 * m22 * m41 - m23 * m42 * m11 - m43 * m12 * m21;
const float d33 = m11 * m22 * m33 + m21 * m32 * m13 + m31 * m12 * m23 - m13 * m22 * m31 - m23 * m32 * m11 - m33 * m12 * m21;
const float det = m11 * d00 - m21 * d01 + m31 * d02 - m41 * d03;
if (det == 0.0f)
{
zero();
return false;
}
const float invDet = 1.0f / det;
m11 = d00 * invDet; m21 = -d10 * invDet; m31 = d20 * invDet; m41 = -d30 * invDet;
m12 = -d01 * invDet; m22 = d11 * invDet; m32 = -d21 * invDet; m42 = d31 * invDet;
m13 = d02 * invDet; m23 = -d12 * invDet; m33 = d22 * invDet; m43 = -d32 * invDet;
m14 = -d03 * invDet; m24 = d13 * invDet; m34 = -d23 * invDet; m44 = d33 * invDet;
return true;
# endif
}
inline void ax::matrix4::transpose()
{
using std::swap;
swap(m12, m21);
swap(m13, m31);
swap(m14, m41);
swap(m23, m32);
swap(m24, m42);
swap(m34, m43);
}
inline void ax::matrix4::translate(float x, float y, float z, matrix_order order/* = matrix_order::prepend*/)
{
combine(matrix4(
1.0f, 0.0f, 0.0f, 0.0f,
0.0f, 1.0f, 0.0f, 0.0f,
0.0f, 0.0f, 1.0f, 0.0f,
x, y, z, 1.0f), order);
}
inline void ax::matrix4::rotate_x(float angle, matrix_order order/* = matrix_order::prepend*/)
{
const auto angleRad = angle * AX_PI / 180.0f;
const auto c = cosf(angleRad);
const auto s = sinf(angleRad);
combine(matrix4(
1.0f, 0.0f, 0.0f, 0.0f,
0.0f, c, -s, 0.0f,
0.0f, s, c, 0.0f,
0.0f, 0.0f, 0.0f, 1.0f), order);
}
inline void ax::matrix4::rotate_y(float angle, matrix_order order/* = matrix_order::prepend*/)
{
const auto angleRad = angle * AX_PI / 180.0f;
const auto c = cosf(angleRad);
const auto s = sinf(angleRad);
combine(matrix4(
c, 0.0f, -s, 0.0f,
0.0f, 1.0f, 0.0f, 0.0f,
s, 0.0f, c, 0.0f,
0.0f, 0.0f, 0.0f, 1.0f), order);
}
inline void ax::matrix4::rotate_z(float angle, matrix_order order/* = matrix_order::prepend*/)
{
const auto angleRad = angle * AX_PI / 180.0f;
const auto c = cosf(angleRad);
const auto s = sinf(angleRad);
combine(matrix4(
c, -s, 0.0f, 0.0f,
s, c, 0.0f, 0.0f,
0.0f, 0.0f, 1.0f, 0.0f,
0.0f, 0.0f, 0.0f, 1.0f), order);
}
inline void ax::matrix4::rotate_axis(float angle, float x, float y, float z, matrix_order order/* = matrix_order::prepend*/)
{
const auto angleRad = angle * AX_PI / 180.0f;
const auto c = cosf(angleRad);
const auto s = sinf(angleRad);
const auto rc = 1.0f - c;
combine(matrix4(
(rc * x * x) + c, (rc * x * y) + (z * s), (rc * x * z) - (y * s), 0.0f,
(rc * x * y) - (z * s), (rc * y * y) + c, (rc * z * y) + (x * s), 0.0f,
(rc * x * z) + (y * s), (rc * y * z) - (x * s), (rc * z * z) + c, 0.0f,
0.0f, 0.0f, 0.0f, 1.0f), order);
}
inline void ax::matrix4::scale(float x, float y, float z, matrix_order order/* = matrix_order::prepend*/)
{
combine(matrix4(
x, 0.0f, 0.0f, 0.0f,
0.0f, y, 0.0f, 0.0f,
0.0f, 0.0f, z, 0.0f,
0.0f, 0.0f, 0.0f, 1.0f), order);
}
inline void ax::matrix4::combine(const matrix4& matrix, matrix_order order/* = matrix_order::prepend*/)
{
if (order == matrix_order::set)
{
if (this != &matrix)
*this = matrix;
return;
}
const auto* am = this;
const auto* bm = &matrix;
if (order == matrix_order::append)
{
using std::swap;
swap(am, bm);
}
*this = matrix4(
am->m11 * bm->m11 + am->m21 * bm->m12 + am->m31 * bm->m13 + am->m41 * bm->m14,
am->m12 * bm->m11 + am->m22 * bm->m12 + am->m32 * bm->m13 + am->m42 * bm->m14,
am->m13 * bm->m11 + am->m23 * bm->m12 + am->m33 * bm->m13 + am->m43 * bm->m14,
am->m14 * bm->m11 + am->m24 * bm->m12 + am->m34 * bm->m13 + am->m44 * bm->m14,
am->m11 * bm->m21 + am->m21 * bm->m22 + am->m31 * bm->m23 + am->m41 * bm->m24,
am->m12 * bm->m21 + am->m22 * bm->m22 + am->m32 * bm->m23 + am->m42 * bm->m24,
am->m13 * bm->m21 + am->m23 * bm->m22 + am->m33 * bm->m23 + am->m43 * bm->m24,
am->m14 * bm->m21 + am->m24 * bm->m22 + am->m34 * bm->m23 + am->m44 * bm->m24,
am->m11 * bm->m31 + am->m21 * bm->m32 + am->m31 * bm->m33 + am->m41 * bm->m34,
am->m12 * bm->m31 + am->m22 * bm->m32 + am->m32 * bm->m33 + am->m42 * bm->m34,
am->m13 * bm->m31 + am->m23 * bm->m32 + am->m33 * bm->m33 + am->m43 * bm->m34,
am->m14 * bm->m31 + am->m24 * bm->m32 + am->m34 * bm->m33 + am->m44 * bm->m34,
am->m11 * bm->m41 + am->m21 * bm->m42 + am->m31 * bm->m43 + am->m41 * bm->m44,
am->m12 * bm->m41 + am->m22 * bm->m42 + am->m32 * bm->m43 + am->m42 * bm->m44,
am->m13 * bm->m41 + am->m23 * bm->m42 + am->m33 * bm->m43 + am->m43 * bm->m44,
am->m14 * bm->m41 + am->m24 * bm->m42 + am->m34 * bm->m43 + am->m44 * bm->m44);
}
inline ax::matrix4 ax::matrix4::inverted() const
{
matrix4 inverted = *this;
inverted.invert();
return inverted;
}
inline ax::matrix4 ax::matrix4::transposed() const
{
matrix4 transposed = *this;
transposed.transpose();
return transposed;
}
//------------------------------------------------------------------------------
namespace ax {
namespace detail {
# if defined(__clang__) || defined(__GNUC__)
template <typename M, typename T>
void transform_points(const M& m, basic_point<T>* points, size_t count);
# else
template <typename M, typename T>
inline void transform_points(const M& m, basic_point<T>* points, size_t count)
{
// UsdLayerManager local patch: static_assert(false, ...) here is
// ill-formed regardless of instantiation (its condition doesn't depend
// on a template parameter) — stricter MSVC (/permissive-, set for MSVC
// >= 1910 in this project's CMakeLists.txt) diagnoses it just from this
// primary template's declaration, even when every real call resolves to
// one of the matrix/matrix4 specializations below and this fallback is
// never instantiated. Make the condition depend on T so it only fires
// if something actually instantiates this unspecialized fallback.
static_assert(sizeof(T) == 0, "This combination of matrix type and point type is not supported");
}
# endif
template <typename M, typename T>
inline void transform_vectors(const M& m, basic_point<T>* points, size_t count)
{
typedef basic_point<T> point_t;
for (size_t i = 0; i < count; ++i, ++points)
{
auto x = m.m11 * points->x + m.m21 * points->y;
auto y = m.m12 * points->x + m.m22 * points->y;
points->x = static_cast<typename point_t::value_type>(x);
points->y = static_cast<typename point_t::value_type>(y);
}
}
template <typename T>
inline void transform_points(const matrix& m, basic_point<T>* points, size_t count)
{
typedef basic_point<T> point_t;
for (size_t i = 0; i < count; ++i, ++points)
{
auto x = m.m11 * points->x + m.m21 * points->y + m.m31;
auto y = m.m12 * points->x + m.m22 * points->y + m.m32;
points->x = static_cast<typename point_t::value_type>(x);
points->y = static_cast<typename point_t::value_type>(y);
}
}
template <typename T>
inline void transform_points(const matrix4& m, basic_point<T>* points, size_t count)
{
typedef basic_point<T> point_t;
for (size_t i = 0; i < count; ++i, ++points)
{
auto x = m.m11 * points->x + m.m21 * points->y + m.m41;
auto y = m.m12 * points->x + m.m22 * points->y + m.m42;
points->x = static_cast<typename point_t::value_type>(x);
points->y = static_cast<typename point_t::value_type>(y);
}
}
} // namespace detail
} // namespace ax
//------------------------------------------------------------------------------
# endif // defined(__cplusplus)
//------------------------------------------------------------------------------
# endif // __AX_MATH_2D_INL__
@@ -0,0 +1,13 @@
#pragma once
#include "imgui.h"
#include "Drawing.h"
namespace ax {
namespace Widgets {
using Drawing::IconType;
void Icon(const ImVec2& size, IconType type, bool filled, const ImVec4& color = ImVec4(1, 1, 1, 1), const ImVec4& innerColor = ImVec4(0, 0, 0, 0));
} // namespace Widgets
} // namespace ax
@@ -0,0 +1,69 @@
//------------------------------------------------------------------------------
// LICENSE
// This software is dual-licensed to the public domain and under the following
// license: you are granted a perpetual, irrevocable license to copy, modify,
// publish, and distribute this file as you see fit.
//
// CREDITS
// Written by Michal Cichon
//------------------------------------------------------------------------------
# pragma once
# include "imgui.h"
# include "ax/Math2D.h"
//------------------------------------------------------------------------------
//namespace ax {
//namespace ImGuiInterop {
//------------------------------------------------------------------------------
// Modern ImGui (1.89.4+) already defines these operators in imgui.h itself
// when IMGUI_DEFINE_MATH_OPERATORS is set before its first include — guard
// against redefinition against this project's ImGui version.
# ifndef IMGUI_DEFINE_MATH_OPERATORS_IMPLEMENTED
static inline bool operator==(const ImVec2& lhs, const ImVec2& rhs) { return lhs.x == rhs.x && lhs.y == rhs.y; }
static inline bool operator!=(const ImVec2& lhs, const ImVec2& rhs) { return lhs.x != rhs.x || lhs.y != rhs.y; }
static inline ImVec2 operator+(const ImVec2& lhs) { return ImVec2( lhs.x, lhs.y); }
static inline ImVec2 operator-(const ImVec2& lhs) { return ImVec2(-lhs.x, -lhs.y); }
static inline ImVec2 operator+(const ImVec2& lhs, const ImVec2& rhs) { return ImVec2(lhs.x+rhs.x, lhs.y+rhs.y); }
static inline ImVec2 operator-(const ImVec2& lhs, const ImVec2& rhs) { return ImVec2(lhs.x-rhs.x, lhs.y-rhs.y); }
static inline ImVec2 operator*(const ImVec2& lhs, float rhs) { return ImVec2(lhs.x * rhs, lhs.y * rhs); }
static inline ImVec2 operator*(float lhs, const ImVec2& rhs) { return ImVec2(lhs * rhs.x, lhs * rhs.y); }
# endif
static inline int roundi(float value) { return static_cast<int>(value); }
static inline ax::point to_point(const ImVec2& value) { return ax::point(roundi(value.x), roundi(value.y)); }
static inline ax::pointf to_pointf(const ImVec2& value) { return ax::pointf(value.x, value.y); }
static inline ax::pointf to_pointf(const ax::point& value) { return ax::pointf(static_cast<float>(value.x), static_cast<float>(value.y)); }
static inline ax::size to_size (const ImVec2& value) { return ax::size (roundi(value.x), roundi(value.y)); }
static inline ax::sizef to_sizef(const ImVec2& value) { return ax::sizef(value.x, value.y); }
static inline ImVec2 to_imvec(const ax::point& value) { return ImVec2(static_cast<float>(value.x), static_cast<float>(value.y)); }
static inline ImVec2 to_imvec(const ax::pointf& value) { return ImVec2(value.x, value.y); }
static inline ImVec2 to_imvec(const ax::size& value) { return ImVec2(static_cast<float>(value.w), static_cast<float>(value.h)); }
static inline ImVec2 to_imvec(const ax::sizef& value) { return ImVec2(value.w, value.h); }
static inline ax::rect ImGui_GetItemRect() { return ax::rect(to_point(ImGui::GetItemRectMin()), to_point(ImGui::GetItemRectMax())); }
//------------------------------------------------------------------------------
struct FringeScaleScope
{
FringeScaleScope(float scale)
: m_LastFringeScale(ImGui::GetWindowDrawList()->_FringeScale)
{
ImGui::GetWindowDrawList()->_FringeScale = scale;
}
~FringeScaleScope()
{
ImGui::GetWindowDrawList()->_FringeScale = m_LastFringeScale;
}
private:
float m_LastFringeScale;
};
//------------------------------------------------------------------------------
//} // namespace ImGuiInterop
//} // namespace ax
@@ -0,0 +1,207 @@
//# include "stdafx.h"
# include "ax/Drawing.h"
# include "ax/Math2D.h"
# include "Interop.h"
# include <cmath>
void ax::Drawing::DrawIcon(ImDrawList* drawList, const ImVec2& a, const ImVec2& b, IconType type, bool filled, ImU32 color, ImU32 innerColor)
{
auto rect = ax::rect(to_point(a), to_point(b));
const auto outline_scale = rect.w / 24.0f;
const auto extra_segments = roundi(2 * outline_scale); // for full circle
if (type == IconType::Flow)
{
const auto origin_scale = rect.w / 24.0f;
const auto offset_x = 1.0f * origin_scale;
const auto offset_y = 0.0f * origin_scale;
const auto margin = (filled ? 2.0f : 2.0f) * origin_scale;
const auto rounding = 0.1f * origin_scale;
const auto tip_round = 0.7f; // percentage of triangle edge (for tip)
//const auto edge_round = 0.7f; // percentage of triangle edge (for corner)
const auto canvas = rectf(
rect.x + margin + offset_x,
rect.y + margin + offset_y,
rect.w - margin * 2.0f,
rect.h - margin * 2.0f);
const auto left = canvas.x + canvas.w * 0.5f * 0.3f;
const auto right = canvas.x + canvas.w - canvas.w * 0.5f * 0.3f;
const auto top = canvas.y + canvas.h * 0.5f * 0.2f;
const auto bottom = canvas.y + canvas.h - canvas.h * 0.5f * 0.2f;
const auto center_y = (top + bottom) * 0.5f;
//const auto angle = AX_PI * 0.5f * 0.5f * 0.5f;
const auto tip_top = ImVec2(canvas.x + canvas.w * 0.5f, top);
const auto tip_right = ImVec2(right, center_y);
const auto tip_bottom = ImVec2(canvas.x + canvas.w * 0.5f, bottom);
drawList->PathLineTo(ImVec2(left, top) + ImVec2(0, rounding));
drawList->PathBezierCubicCurveTo(
ImVec2(left, top),
ImVec2(left, top),
ImVec2(left, top) + ImVec2(rounding, 0));
drawList->PathLineTo(tip_top);
drawList->PathLineTo(tip_top + (tip_right - tip_top) * tip_round);
drawList->PathBezierCubicCurveTo(
tip_right,
tip_right,
tip_bottom + (tip_right - tip_bottom) * tip_round);
drawList->PathLineTo(tip_bottom);
drawList->PathLineTo(ImVec2(left, bottom) + ImVec2(rounding, 0));
drawList->PathBezierCubicCurveTo(
ImVec2(left, bottom),
ImVec2(left, bottom),
ImVec2(left, bottom) - ImVec2(0, rounding));
if (!filled)
{
if (innerColor & 0xFF000000)
drawList->AddConvexPolyFilled(drawList->_Path.Data, drawList->_Path.Size, innerColor);
drawList->PathStroke(color, true, 2.0f * outline_scale);
}
else
drawList->PathFillConvex(color);
}
else
{
auto triangleStart = rect.center_x() + 0.32f * rect.w;
rect.x -= roundi(rect.w * 0.25f * 0.25f);
if (type == IconType::Circle)
{
const auto c = to_imvec(rect.center());
if (!filled)
{
const auto r = 0.5f * rect.w / 2.0f - 0.5f;
if (innerColor & 0xFF000000)
drawList->AddCircleFilled(c, r, innerColor, 12 + extra_segments);
drawList->AddCircle(c, r, color, 12 + extra_segments, 2.0f * outline_scale);
}
else
drawList->AddCircleFilled(c, 0.5f * rect.w / 2.0f, color, 12 + extra_segments);
}
if (type == IconType::Square)
{
if (filled)
{
const auto r = 0.5f * rect.w / 2.0f;
const auto p0 = to_imvec(rect.center()) - ImVec2(r, r);
const auto p1 = to_imvec(rect.center()) + ImVec2(r, r);
drawList->AddRectFilled(p0, p1, color, 0, 15 + extra_segments);
}
else
{
const auto r = 0.5f * rect.w / 2.0f - 0.5f;
const auto p0 = to_imvec(rect.center()) - ImVec2(r, r);
const auto p1 = to_imvec(rect.center()) + ImVec2(r, r);
if (innerColor & 0xFF000000)
drawList->AddRectFilled(p0, p1, innerColor, 0, 15 + extra_segments);
drawList->AddRect(p0, p1, color, 0, 15 + extra_segments, 2.0f * outline_scale);
}
}
if (type == IconType::Grid)
{
const auto r = 0.5f * rect.w / 2.0f;
const auto w = ceilf(r / 3.0f);
const auto baseTl = ImVec2(floorf(rect.center_x() - w * 2.5f), floorf(rect.center_y() - w * 2.5f));
const auto baseBr = ImVec2(floorf(baseTl.x + w), floorf(baseTl.y + w));
auto tl = baseTl;
auto br = baseBr;
for (int i = 0; i < 3; ++i)
{
tl.x = baseTl.x;
br.x = baseBr.x;
drawList->AddRectFilled(tl, br, color);
tl.x += w * 2;
br.x += w * 2;
if (i != 1 || filled)
drawList->AddRectFilled(tl, br, color);
tl.x += w * 2;
br.x += w * 2;
drawList->AddRectFilled(tl, br, color);
tl.y += w * 2;
br.y += w * 2;
}
triangleStart = br.x + w + 1.0f / 24.0f * rect.w;
}
if (type == IconType::RoundSquare)
{
if (filled)
{
const auto r = 0.5f * rect.w / 2.0f;
const auto cr = r * 0.5f;
const auto p0 = to_imvec(rect.center()) - ImVec2(r, r);
const auto p1 = to_imvec(rect.center()) + ImVec2(r, r);
drawList->AddRectFilled(p0, p1, color, cr, 15);
}
else
{
const auto r = 0.5f * rect.w / 2.0f - 0.5f;
const auto cr = r * 0.5f;
const auto p0 = to_imvec(rect.center()) - ImVec2(r, r);
const auto p1 = to_imvec(rect.center()) + ImVec2(r, r);
if (innerColor & 0xFF000000)
drawList->AddRectFilled(p0, p1, innerColor, cr, 15);
drawList->AddRect(p0, p1, color, cr, 15, 2.0f * outline_scale);
}
}
else if (type == IconType::Diamond)
{
if (filled)
{
const auto r = 0.607f * rect.w / 2.0f;
const auto c = rect.center();
drawList->PathLineTo(to_imvec(c) + ImVec2( 0, -r));
drawList->PathLineTo(to_imvec(c) + ImVec2( r, 0));
drawList->PathLineTo(to_imvec(c) + ImVec2( 0, r));
drawList->PathLineTo(to_imvec(c) + ImVec2(-r, 0));
drawList->PathFillConvex(color);
}
else
{
const auto r = 0.607f * rect.w / 2.0f - 0.5f;
const auto c = rect.center();
drawList->PathLineTo(to_imvec(c) + ImVec2( 0, -r));
drawList->PathLineTo(to_imvec(c) + ImVec2( r, 0));
drawList->PathLineTo(to_imvec(c) + ImVec2( 0, r));
drawList->PathLineTo(to_imvec(c) + ImVec2(-r, 0));
if (innerColor & 0xFF000000)
drawList->AddConvexPolyFilled(drawList->_Path.Data, drawList->_Path.Size, innerColor);
drawList->PathStroke(color, true, 2.0f * outline_scale);
}
}
else
{
const auto triangleTip = triangleStart + rect.w * (0.45f - 0.32f);
drawList->AddTriangleFilled(
ImVec2(ceilf(triangleTip), rect.top() + rect.h * 0.5f),
ImVec2(triangleStart, rect.center_y() + 0.15f * rect.h),
ImVec2(triangleStart, rect.center_y() - 0.15f * rect.h),
color);
}
}
}
@@ -0,0 +1,15 @@
#include "ax/Widgets.h"
#include "Interop.h"
void ax::Widgets::Icon(const ImVec2& size, IconType type, bool filled, const ImVec4& color/* = ImVec4(1, 1, 1, 1)*/, const ImVec4& innerColor/* = ImVec4(0, 0, 0, 0)*/)
{
if (ImGui::IsRectVisible(size))
{
auto cursorPos = ImGui::GetCursorScreenPos();
auto drawList = ImGui::GetWindowDrawList();
ax::Drawing::DrawIcon(drawList, cursorPos, cursorPos + size, type, filled, ImColor(color), ImColor(innerColor));
}
ImGui::Dummy(size);
}
@@ -0,0 +1,442 @@
//------------------------------------------------------------------------------
// LICENSE
// This software is dual-licensed to the public domain and under the following
// license: you are granted a perpetual, irrevocable license to copy, modify,
// publish, and distribute this file as you see fit.
//
// CREDITS
// Written by Michal Cichon
//------------------------------------------------------------------------------
# ifndef __IMGUI_NODE_EDITOR_H__
# define __IMGUI_NODE_EDITOR_H__
# pragma once
//------------------------------------------------------------------------------
# include <imgui.h>
# include <cstdint> // std::uintXX_t
# include <utility> // std::move
//------------------------------------------------------------------------------
namespace ax {
namespace NodeEditor {
//------------------------------------------------------------------------------
struct NodeId;
struct LinkId;
struct PinId;
//------------------------------------------------------------------------------
enum class SaveReasonFlags: uint32_t
{
None = 0x00000000,
Navigation = 0x00000001,
Position = 0x00000002,
Size = 0x00000004,
Selection = 0x00000008,
User = 0x00000010
};
inline SaveReasonFlags operator |(SaveReasonFlags lhs, SaveReasonFlags rhs) { return static_cast<SaveReasonFlags>(static_cast<uint32_t>(lhs) | static_cast<uint32_t>(rhs)); }
inline SaveReasonFlags operator &(SaveReasonFlags lhs, SaveReasonFlags rhs) { return static_cast<SaveReasonFlags>(static_cast<uint32_t>(lhs) & static_cast<uint32_t>(rhs)); }
using ConfigSaveSettings = bool (*)(const char* data, size_t size, SaveReasonFlags reason, void* userPointer);
using ConfigLoadSettings = size_t (*)(char* data, void* userPointer);
using ConfigSaveNodeSettings = bool (*)(NodeId nodeId, const char* data, size_t size, SaveReasonFlags reason, void* userPointer);
using ConfigLoadNodeSettings = size_t (*)(NodeId nodeId, char* data, void* userPointer);
using ConfigSession = void (*)(void* userPointer);
struct Config
{
const char* SettingsFile;
ConfigSession BeginSaveSession;
ConfigSession EndSaveSession;
ConfigSaveSettings SaveSettings;
ConfigLoadSettings LoadSettings;
ConfigSaveNodeSettings SaveNodeSettings;
ConfigLoadNodeSettings LoadNodeSettings;
void* UserPointer;
Config()
: SettingsFile("NodeEditor.json")
, BeginSaveSession(nullptr)
, EndSaveSession(nullptr)
, SaveSettings(nullptr)
, LoadSettings(nullptr)
, SaveNodeSettings(nullptr)
, LoadNodeSettings(nullptr)
, UserPointer(nullptr)
{
}
};
//------------------------------------------------------------------------------
enum class PinKind
{
Input,
Output
};
//------------------------------------------------------------------------------
enum StyleColor
{
StyleColor_Bg,
StyleColor_Grid,
StyleColor_NodeBg,
StyleColor_NodeBorder,
StyleColor_HovNodeBorder,
StyleColor_SelNodeBorder,
StyleColor_NodeSelRect,
StyleColor_NodeSelRectBorder,
StyleColor_HovLinkBorder,
StyleColor_SelLinkBorder,
StyleColor_LinkSelRect,
StyleColor_LinkSelRectBorder,
StyleColor_PinRect,
StyleColor_PinRectBorder,
StyleColor_Flow,
StyleColor_FlowMarker,
StyleColor_GroupBg,
StyleColor_GroupBorder,
StyleColor_Count
};
enum StyleVar
{
StyleVar_NodePadding,
StyleVar_NodeRounding,
StyleVar_NodeBorderWidth,
StyleVar_HoveredNodeBorderWidth,
StyleVar_SelectedNodeBorderWidth,
StyleVar_PinRounding,
StyleVar_PinBorderWidth,
StyleVar_LinkStrength,
StyleVar_SourceDirection,
StyleVar_TargetDirection,
StyleVar_ScrollDuration,
StyleVar_FlowMarkerDistance,
StyleVar_FlowSpeed,
StyleVar_FlowDuration,
StyleVar_PivotAlignment,
StyleVar_PivotSize,
StyleVar_PivotScale,
StyleVar_PinCorners,
StyleVar_PinRadius,
StyleVar_PinArrowSize,
StyleVar_PinArrowWidth,
StyleVar_GroupRounding,
StyleVar_GroupBorderWidth,
};
struct Style
{
ImVec4 NodePadding;
float NodeRounding;
float NodeBorderWidth;
float HoveredNodeBorderWidth;
float SelectedNodeBorderWidth;
float PinRounding;
float PinBorderWidth;
float LinkStrength;
ImVec2 SourceDirection;
ImVec2 TargetDirection;
float ScrollDuration;
float FlowMarkerDistance;
float FlowSpeed;
float FlowDuration;
ImVec2 PivotAlignment;
ImVec2 PivotSize;
ImVec2 PivotScale;
float PinCorners;
float PinRadius;
float PinArrowSize;
float PinArrowWidth;
float GroupRounding;
float GroupBorderWidth;
ImVec4 Colors[StyleColor_Count];
Style()
{
NodePadding = ImVec4(8, 8, 8, 8);
NodeRounding = 12.0f;
NodeBorderWidth = 1.5f;
HoveredNodeBorderWidth = 3.5f;
SelectedNodeBorderWidth = 3.5f;
PinRounding = 4.0f;
PinBorderWidth = 0.0f;
LinkStrength = 100.0f;
SourceDirection = ImVec2(1.0f, 0.0f);
TargetDirection = ImVec2(-1.0f, 0.0f);
ScrollDuration = 0.35f;
FlowMarkerDistance = 30.0f;
FlowSpeed = 150.0f;
FlowDuration = 2.0f;
PivotAlignment = ImVec2(0.5f, 0.5f);
PivotSize = ImVec2(-1, -1);
PivotScale = ImVec2(1, 1);
PinCorners = ImDrawFlags_RoundCornersAll; // ImDrawCornerFlags_All renamed upstream in ImGui
PinRadius = 0.0f;
PinArrowSize = 0.0f;
PinArrowWidth = 0.0f;
GroupRounding = 6.0f;
GroupBorderWidth = 1.0f;
Colors[StyleColor_Bg] = ImColor( 60, 60, 70, 200);
Colors[StyleColor_Grid] = ImColor(120, 120, 120, 40);
Colors[StyleColor_NodeBg] = ImColor( 32, 32, 32, 200);
Colors[StyleColor_NodeBorder] = ImColor(255, 255, 255, 96);
Colors[StyleColor_HovNodeBorder] = ImColor( 50, 176, 255, 255);
Colors[StyleColor_SelNodeBorder] = ImColor(255, 176, 50, 255);
Colors[StyleColor_NodeSelRect] = ImColor( 5, 130, 255, 64);
Colors[StyleColor_NodeSelRectBorder] = ImColor( 5, 130, 255, 128);
Colors[StyleColor_HovLinkBorder] = ImColor( 50, 176, 255, 255);
Colors[StyleColor_SelLinkBorder] = ImColor(255, 176, 50, 255);
Colors[StyleColor_LinkSelRect] = ImColor( 5, 130, 255, 64);
Colors[StyleColor_LinkSelRectBorder] = ImColor( 5, 130, 255, 128);
Colors[StyleColor_PinRect] = ImColor( 60, 180, 255, 100);
Colors[StyleColor_PinRectBorder] = ImColor( 60, 180, 255, 128);
Colors[StyleColor_Flow] = ImColor(255, 128, 64, 255);
Colors[StyleColor_FlowMarker] = ImColor(255, 128, 64, 255);
Colors[StyleColor_GroupBg] = ImColor( 0, 0, 0, 160);
Colors[StyleColor_GroupBorder] = ImColor(255, 255, 255, 32);
}
};
//------------------------------------------------------------------------------
struct EditorContext;
//------------------------------------------------------------------------------
void SetCurrentEditor(EditorContext* ctx);
EditorContext* GetCurrentEditor();
EditorContext* CreateEditor(const Config* config = nullptr);
void DestroyEditor(EditorContext* ctx);
Style& GetStyle();
const char* GetStyleColorName(StyleColor colorIndex);
void PushStyleColor(StyleColor colorIndex, const ImVec4& color);
void PopStyleColor(int count = 1);
void PushStyleVar(StyleVar varIndex, float value);
void PushStyleVar(StyleVar varIndex, const ImVec2& value);
void PushStyleVar(StyleVar varIndex, const ImVec4& value);
void PopStyleVar(int count = 1);
void Begin(const char* id, const ImVec2& size = ImVec2(0, 0));
void End();
void BeginNode(NodeId id);
void BeginPin(PinId id, PinKind kind);
void PinRect(const ImVec2& a, const ImVec2& b);
void PinPivotRect(const ImVec2& a, const ImVec2& b);
void PinPivotSize(const ImVec2& size);
void PinPivotScale(const ImVec2& scale);
void PinPivotAlignment(const ImVec2& alignment);
void EndPin();
void Group(const ImVec2& size);
void EndNode();
bool BeginGroupHint(NodeId nodeId);
ImVec2 GetGroupMin();
ImVec2 GetGroupMax();
ImDrawList* GetHintForegroundDrawList();
ImDrawList* GetHintBackgroundDrawList();
void EndGroupHint();
// TODO: Add a way to manage node background channels
ImDrawList* GetNodeBackgroundDrawList(NodeId nodeId);
bool Link(LinkId id, PinId startPinId, PinId endPinId, const ImVec4& color = ImVec4(1, 1, 1, 1), float thickness = 1.0f);
void Flow(LinkId linkId);
bool BeginCreate(const ImVec4& color = ImVec4(1, 1, 1, 1), float thickness = 1.0f);
bool QueryNewLink(PinId* startId, PinId* endId);
bool QueryNewLink(PinId* startId, PinId* endId, const ImVec4& color, float thickness = 1.0f);
bool QueryNewNode(PinId* pinId);
bool QueryNewNode(PinId* pinId, const ImVec4& color, float thickness = 1.0f);
bool AcceptNewItem();
bool AcceptNewItem(const ImVec4& color, float thickness = 1.0f);
void RejectNewItem();
void RejectNewItem(const ImVec4& color, float thickness = 1.0f);
void EndCreate();
bool BeginDelete();
bool QueryDeletedLink(LinkId* linkId, PinId* startId = nullptr, PinId* endId = nullptr);
bool QueryDeletedNode(NodeId* nodeId);
bool AcceptDeletedItem();
void RejectDeletedItem();
void EndDelete();
void SetNodePosition(NodeId nodeId, const ImVec2& editorPosition);
ImVec2 GetNodePosition(NodeId nodeId);
ImVec2 GetNodeSize(NodeId nodeId);
void CenterNodeOnScreen(NodeId nodeId);
void RestoreNodeState(NodeId nodeId);
void Suspend();
void Resume();
bool IsSuspended();
bool IsActive();
bool HasSelectionChanged();
int GetSelectedObjectCount();
int GetSelectedNodes(NodeId* nodes, int size);
int GetSelectedLinks(LinkId* links, int size);
void ClearSelection();
void SelectNode(NodeId nodeId, bool append = false);
void SelectLink(LinkId linkId, bool append = false);
void DeselectNode(NodeId nodeId);
void DeselectLink(LinkId linkId);
bool DeleteNode(NodeId nodeId);
bool DeleteLink(LinkId linkId);
void NavigateToContent(float duration = -1);
void NavigateToSelection(bool zoomIn = false, float duration = -1);
bool ShowNodeContextMenu(NodeId* nodeId);
bool ShowPinContextMenu(PinId* pinId);
bool ShowLinkContextMenu(LinkId* linkId);
bool ShowBackgroundContextMenu();
void EnableShortcuts(bool enable);
bool AreShortcutsEnabled();
bool BeginShortcut();
bool AcceptCut();
bool AcceptCopy();
bool AcceptPaste();
bool AcceptDuplicate();
bool AcceptCreateNode();
int GetActionContextSize();
int GetActionContextNodes(NodeId* nodes, int size);
int GetActionContextLinks(LinkId* links, int size);
void EndShortcut();
float GetCurrentZoom();
NodeId GetDoubleClickedNode();
PinId GetDoubleClickedPin();
LinkId GetDoubleClickedLink();
bool IsBackgroundClicked();
bool IsBackgroundDoubleClicked();
bool PinHadAnyLinks(PinId pinId);
ImVec2 GetScreenSize();
ImVec2 ScreenToCanvas(const ImVec2& pos);
ImVec2 CanvasToScreen(const ImVec2& pos);
//------------------------------------------------------------------------------
namespace Details {
template <typename T, typename Tag>
struct SafeType
{
SafeType(T t)
: m_Value(std::move(t))
{
}
SafeType(const SafeType&) = default;
template <typename T2, typename Tag2>
SafeType(
const SafeType
<
typename std::enable_if<!std::is_same<T, T2>::value, T2>::type,
typename std::enable_if<!std::is_same<Tag, Tag2>::value, Tag2>::type
>&) = delete;
SafeType& operator=(const SafeType&) = default;
explicit operator T() const { return Get(); }
T Get() const { return m_Value; }
private:
T m_Value;
};
template <typename Tag>
struct SafePointerType
: SafeType<uintptr_t, Tag>
{
static const Tag Invalid;
using SafeType<uintptr_t, Tag>::SafeType;
SafePointerType()
: SafePointerType(Invalid)
{
}
template <typename T = void> explicit SafePointerType(T* ptr): SafePointerType(reinterpret_cast<uintptr_t>(ptr)) {}
template <typename T = void> T* AsPointer() const { return reinterpret_cast<T*>(this->Get()); }
explicit operator bool() const { return *this != Invalid; }
};
template <typename Tag>
const Tag SafePointerType<Tag>::Invalid = { 0 };
template <typename Tag>
inline bool operator==(const SafePointerType<Tag>& lhs, const SafePointerType<Tag>& rhs)
{
return lhs.Get() == rhs.Get();
}
template <typename Tag>
inline bool operator!=(const SafePointerType<Tag>& lhs, const SafePointerType<Tag>& rhs)
{
return lhs.Get() != rhs.Get();
}
} // namespace Details
struct NodeId final: Details::SafePointerType<NodeId>
{
using SafePointerType::SafePointerType;
};
struct LinkId final: Details::SafePointerType<LinkId>
{
using SafePointerType::SafePointerType;
};
struct PinId final: Details::SafePointerType<PinId>
{
using SafePointerType::SafePointerType;
};
//------------------------------------------------------------------------------
} // namespace Editor
} // namespace ax
//------------------------------------------------------------------------------
# endif // __IMGUI_NODE_EDITOR_H__
+21
View File
@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2019 Michał Cichoń
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+814
View File
@@ -0,0 +1,814 @@
// Crude implementation of JSON value object and parser.
//
// LICENSE
// This software is dual-licensed to the public domain and under the following
// license: you are granted a perpetual, irrevocable license to copy, modify,
// publish, and distribute this file as you see fit.
//
// CREDITS
// Written by Michal Cichon
# include "crude_json.h"
# include <iomanip>
# include <limits>
# include <cstdlib>
# include <clocale>
# include <cmath>
# include <cstring>
namespace crude_json {
value::value(value&& other)
: m_Type(other.m_Type)
{
switch (m_Type)
{
case type_t::object: construct(m_Storage, std::move( *object_ptr(other.m_Storage))); break;
case type_t::array: construct(m_Storage, std::move( *array_ptr(other.m_Storage))); break;
case type_t::string: construct(m_Storage, std::move( *string_ptr(other.m_Storage))); break;
case type_t::boolean: construct(m_Storage, std::move(*boolean_ptr(other.m_Storage))); break;
case type_t::number: construct(m_Storage, std::move( *number_ptr(other.m_Storage))); break;
default: break;
}
destruct(other.m_Storage, other.m_Type);
other.m_Type = type_t::null;
}
value::value(const value& other)
: m_Type(other.m_Type)
{
switch (m_Type)
{
case type_t::object: construct(m_Storage, *object_ptr(other.m_Storage)); break;
case type_t::array: construct(m_Storage, *array_ptr(other.m_Storage)); break;
case type_t::string: construct(m_Storage, *string_ptr(other.m_Storage)); break;
case type_t::boolean: construct(m_Storage, *boolean_ptr(other.m_Storage)); break;
case type_t::number: construct(m_Storage, *number_ptr(other.m_Storage)); break;
default: break;
}
}
value& value::operator[](size_t index)
{
if (is_null())
m_Type = construct(m_Storage, type_t::array);
if (is_array())
{
auto& v = *array_ptr(m_Storage);
if (index >= v.size())
v.insert(v.end(), index - v.size() + 1, value());
return v[index];
}
CRUDE_ASSERT(false && "operator[] on unsupported type");
std::terminate();
}
const value& value::operator[](size_t index) const
{
if (is_array())
return (*array_ptr(m_Storage))[index];
CRUDE_ASSERT(false && "operator[] on unsupported type");
std::terminate();
}
value& value::operator[](const string& key)
{
if (is_null())
m_Type = construct(m_Storage, type_t::object);
if (is_object())
return (*object_ptr(m_Storage))[key];
CRUDE_ASSERT(false && "operator[] on unsupported type");
std::terminate();
}
const value& value::operator[](const string& key) const
{
if (is_object())
{
auto& o = *object_ptr(m_Storage);
auto it = o.find(key);
CRUDE_ASSERT(it != o.end());
return it->second;
}
CRUDE_ASSERT(false && "operator[] on unsupported type");
std::terminate();
}
bool value::contains(const string& key) const
{
if (is_object())
{
auto& o = *object_ptr(m_Storage);
auto it = o.find(key);
return it != o.end();
}
return false;
}
void value::push_back(const value& value)
{
if (is_null())
m_Type = construct(m_Storage, type_t::array);
if (is_array())
{
auto& v = *array_ptr(m_Storage);
v.push_back(value);
}
else
{
CRUDE_ASSERT(false && "operator[] on unsupported type");
std::terminate();
}
}
void value::push_back(value&& value)
{
if (is_null())
m_Type = construct(m_Storage, type_t::array);
if (is_array())
{
auto& v = *array_ptr(m_Storage);
v.push_back(std::move(value));
}
else
{
CRUDE_ASSERT(false && "operator[] on unsupported type");
std::terminate();
}
}
void value::swap(value& other)
{
using std::swap;
if (m_Type == other.m_Type)
{
switch (m_Type)
{
case type_t::object: swap(*object_ptr(m_Storage), *object_ptr(other.m_Storage)); break;
case type_t::array: swap(*array_ptr(m_Storage), *array_ptr(other.m_Storage)); break;
case type_t::string: swap(*string_ptr(m_Storage), *string_ptr(other.m_Storage)); break;
case type_t::boolean: swap(*boolean_ptr(m_Storage), *boolean_ptr(other.m_Storage)); break;
case type_t::number: swap(*number_ptr(m_Storage), *number_ptr(other.m_Storage)); break;
default: break;
}
}
else
{
value tmp(std::move(other));
other.~value();
new (&other) value(std::move(*this));
this->~value();
new (this) value(std::move(tmp));
}
}
string value::dump(const int indent, const char indent_char) const
{
dump_context_t context(indent, indent_char);
context.out.precision(std::numeric_limits<double>::max_digits10 + 1);
context.out << std::defaultfloat;
dump(context, 0);
return context.out.str();
}
void value::dump_context_t::write_indent(int level)
{
if (indent <= 0 || level == 0)
return;
out.fill(indent_char);
out.width(indent * level);
out << indent_char;
out.width(0);
}
void value::dump_context_t::write_separator()
{
if (indent < 0)
return;
out.put(' ');
}
void value::dump_context_t::write_newline()
{
if (indent < 0)
return;
out.put('\n');
}
void value::dump(dump_context_t& context, int level) const
{
context.write_indent(level);
switch (m_Type)
{
case type_t::null:
context.out << "null";
break;
case type_t::object:
context.out << '{';
{
context.write_newline();
bool first = true;
for (auto& entry : *object_ptr(m_Storage))
{
if (!first) { context.out << ','; context.write_newline(); } else first = false;
context.write_indent(level + 1);
context.out << '\"' << entry.first << "\":";
if (!entry.second.is_structured())
{
context.write_separator();
entry.second.dump(context, 0);
}
else
{
context.write_newline();
entry.second.dump(context, level + 1);
}
}
if (!first)
context.write_newline();
}
context.write_indent(level);
context.out << '}';
break;
case type_t::array:
context.out << '[';
{
context.write_newline();
bool first = true;
for (auto& entry : *array_ptr(m_Storage))
{
if (!first) { context.out << ','; context.write_newline(); } else first = false;
if (!entry.is_structured())
{
context.write_indent(level + 1);
entry.dump(context, 0);
}
else
{
entry.dump(context, level + 1);
}
}
if (!first)
context.write_newline();
}
context.write_indent(level);
context.out << ']';
break;
case type_t::string:
context.out << '\"';
if (string_ptr(m_Storage)->find_first_of("\"\\/\b\f\n\r") != string::npos || string_ptr(m_Storage)->find('\0') != string::npos)
{
for (auto c : *string_ptr(m_Storage))
{
if (c == '\"') context.out << "\\\"";
else if (c == '\\') context.out << "\\\\";
else if (c == '/') context.out << "\\/";
else if (c == '\b') context.out << "\\b";
else if (c == '\f') context.out << "\\f";
else if (c == '\n') context.out << "\\n";
else if (c == '\r') context.out << "\\r";
else if (c == '\t') context.out << "\\t";
else if (c == 0) context.out << "\\u0000";
else context.out << c;
}
}
else
context.out << *string_ptr(m_Storage);
context.out << '\"';
break;
case type_t::boolean:
if (*boolean_ptr(m_Storage))
context.out << "true";
else
context.out << "false";
break;
case type_t::number:
context.out << *number_ptr(m_Storage);
break;
default:
break;
}
}
struct value::parser
{
parser(const char* begin, const char* end)
: m_Cursor(begin)
, m_End(end)
{
}
value parse()
{
value v;
// Switch to C locale to make strtod and strtol work as expected
auto previous_locale = std::setlocale(LC_NUMERIC, "C");
// Accept single value only when end of the stream is reached.
if (!accept_element(v) || !eof())
v = value(type_t::discarded);
if (previous_locale && strcmp(previous_locale, "C") != 0)
std::setlocale(LC_NUMERIC, previous_locale);
return v;
}
private:
struct cursor_state
{
cursor_state(parser* p)
: m_Owner(p)
, m_LastCursor(p->m_Cursor)
{
}
void reset()
{
m_Owner->m_Cursor = m_LastCursor;
}
bool operator()(bool accept)
{
if (!accept)
reset();
else
m_LastCursor = m_Owner->m_Cursor;
return accept;
}
private:
parser* m_Owner;
const char* m_LastCursor;
};
cursor_state state()
{
return cursor_state(this);
}
bool accept_value(value& result)
{
return accept_object(result)
|| accept_array(result)
|| accept_string(result)
|| accept_number(result)
|| accept_boolean(result)
|| accept_null(result);
}
bool accept_object(value& result)
{
auto s = state();
object o;
if (s(accept('{') && accept_ws() && accept('}')))
{
result = o;
return true;
}
else if (s(accept('{') && accept_members(o) && accept('}')))
{
result = std::move(o);
return true;
}
return false;
}
bool accept_members(object& o)
{
if (!accept_member(o))
return false;
while (true)
{
auto s = state();
if (!s(accept(',') && accept_member(o)))
break;
}
return true;
}
bool accept_member(object& o)
{
auto s = state();
value key;
value v;
if (s(accept_ws() && accept_string(key) && accept_ws() && accept(':') && accept_element(v)))
{
o.emplace(std::move(key.get<string>()), std::move(v));
return true;
}
return false;
}
bool accept_array(value& result)
{
auto s = state();
if (s(accept('[') && accept_ws() && accept(']')))
{
result = array();
return true;
}
array a;
if (s(accept('[') && accept_elements(a) && accept(']')))
{
result = std::move(a);
return true;
}
return false;
}
bool accept_elements(array& a)
{
value v;
if (!accept_element(v))
return false;
a.emplace_back(std::move(v));
while (true)
{
auto s = state();
v = nullptr;
if (!s(accept(',') && accept_element(v)))
break;
a.emplace_back(std::move(v));
}
return true;
}
bool accept_element(value& result)
{
auto s = state();
return s(accept_ws() && accept_value(result) && accept_ws());
}
bool accept_string(value& result)
{
auto s = state();
string v;
if (s(accept('\"') && accept_characters(v) && accept('\"')))
{
result = std::move(v);
return true;
}
else
return false;
}
bool accept_characters(string& result)
{
int c;
while (accept_character(c))
{
CRUDE_ASSERT(c < 128); // #todo: convert characters > 127 to UTF-8
result.push_back(static_cast<char>(c));
}
return true;
}
bool accept_character(int& c)
{
auto s = state();
if (accept('\\'))
{
return accept_escape(c);
}
else if (expect('\"'))
return false;
// #todo: Handle UTF-8 sequences.
return s((c = peek()) >= 0) && advance();
}
bool accept_escape(int& c)
{
if (accept('\"')) { c = '\"'; return true; }
if (accept('\\')) { c = '\\'; return true; }
if (accept('/')) { c = '/'; return true; }
if (accept('b')) { c = '\b'; return true; }
if (accept('f')) { c = '\f'; return true; }
if (accept('n')) { c = '\n'; return true; }
if (accept('r')) { c = '\r'; return true; }
if (accept('t')) { c = '\t'; return true; }
auto s = state();
string hex;
hex.reserve(4);
if (s(accept('u') && accept_hex(hex) && accept_hex(hex) && accept_hex(hex) && accept_hex(hex)))
{
char* end = nullptr;
auto v = std::strtol(hex.c_str(), &end, 16);
if (end != hex.c_str() + hex.size())
return false;
c = v;
return true;
}
return false;
}
bool accept_hex(string& result)
{
if (accept_digit(result))
return true;
auto c = peek();
if ((c >= 'A' && c <= 'F') || (c >= 'a' && c <= 'f'))
{
advance();
result.push_back(c);
return true;
}
return false;
}
bool accept_number(value& result)
{
auto s = state();
string n;
if (s(accept_int(n) && accept_frac(n) && accept_exp(n)))
{
char* end = nullptr;
auto v = std::strtod(n.c_str(), &end);
if (end != n.c_str() + n.size())
return false;
if (!std::isnormal(v))
return false;
result = v;
return true;
}
return false;
}
bool accept_int(string& result)
{
auto s = state();
string part;
if (s(accept_onenine(part) && accept_digits(part)))
{
result += std::move(part);
return true;
}
part.resize(0);
if (accept_digit(part))
{
result += std::move(part);
return true;
}
part.resize(0);
if (s(accept('-') && accept_onenine(part) && accept_digits(part)))
{
result += '-';
result += std::move(part);
return true;
}
part.resize(0);
if (s(accept('-') && accept_digit(part)))
{
result += '-';
result += std::move(part);
return true;
}
return false;
}
bool accept_digits(string& result)
{
string part;
if (!accept_digit(part))
return false;
while (accept_digit(part))
;
result += std::move(part);
return true;
}
bool accept_digit(string& result)
{
if (accept('0'))
{
result.push_back('0');
return true;
}
else if (accept_onenine(result))
return true;
return false;
}
bool accept_onenine(string& result)
{
auto c = peek();
if (c >= '1' && c <= '9')
{
result.push_back(static_cast<char>(c));
return advance();
}
return false;
}
bool accept_frac(string& result)
{
auto s = state();
string part;
if (s(accept('.') && accept_digits(part)))
{
result += '.';
result += std::move(part);
}
return true;
}
bool accept_exp(string& result)
{
auto s = state();
string part;
if (s(accept('e') && accept_sign(part) && accept_digits(part)))
{
result += 'e';
result += std::move(part);
return true;
}
part.resize(0);
if (s(accept('E') && accept_sign(part) && accept_digits(part)))
{
result += 'E';
result += std::move(part);
}
return true;
}
bool accept_sign(string& result)
{
if (accept('+'))
result.push_back('+');
else if (accept('-'))
result.push_back('-');
return true;
}
bool accept_ws()
{
while (expect('\x09') || expect('\x0A') || expect('\x0D') || expect('\x20'))
advance();
return true;
}
bool accept_boolean(value& result)
{
if (accept("true"))
{
result = true;
return true;
}
else if (accept("false"))
{
result = false;
return true;
}
return false;
}
bool accept_null(value& result)
{
if (accept("null"))
{
result = nullptr;
return true;
}
return false;
}
bool accept(char c)
{
if (expect(c))
return advance();
else
return false;
}
bool accept(const char* str)
{
auto last = m_Cursor;
while (*str)
{
if (eof() || *str != *m_Cursor)
{
m_Cursor = last;
return false;
}
advance();
++str;
}
return true;
}
int peek() const
{
if (!eof())
return *m_Cursor;
else
return -1;
}
bool expect(char c)
{
return peek() == c;
}
bool advance(int count = 1)
{
if (m_Cursor + count > m_End)
{
m_Cursor = m_End;
return false;
}
m_Cursor += count;
return true;
}
bool eof() const
{
return m_Cursor == m_End;
}
const char* m_Cursor;
const char* m_End;
};
value value::parse(const string& data)
{
auto p = parser(data.c_str(), data.c_str() + data.size());
auto v = p.parse();
return v;
}
} // namespace crude_json
+223
View File
@@ -0,0 +1,223 @@
// Crude implementation of JSON value object and parser.
//
// LICENSE
// This software is dual-licensed to the public domain and under the following
// license: you are granted a perpetual, irrevocable license to copy, modify,
// publish, and distribute this file as you see fit.
//
// CREDITS
// Written by Michal Cichon
# ifndef __CRUDE_JSON_H__
# define __CRUDE_JSON_H__
# pragma once
# include <type_traits>
# include <string>
# include <vector>
# include <map>
# include <cstddef>
# include <algorithm>
# include <sstream>
# ifndef CRUDE_ASSERT
# include <cassert>
# define CRUDE_ASSERT(expr) assert(expr)
# endif
namespace crude_json {
struct value;
using string = std::string;
using object = std::map<string, value>;
using array = std::vector<value>;
using number = double;
using boolean = bool;
using null = std::nullptr_t;
enum class type_t
{
null,
object,
array,
string,
boolean,
number,
discarded
};
struct value
{
value(type_t type = type_t::null): m_Type(construct(m_Storage, type)) {}
value(value&& other);
value(const value& other);
value( null) : m_Type(construct(m_Storage, null())) {}
value( object&& v): m_Type(construct(m_Storage, std::move(v))) {}
value(const object& v): m_Type(construct(m_Storage, v)) {}
value( array&& v): m_Type(construct(m_Storage, std::move(v))) {}
value(const array& v): m_Type(construct(m_Storage, v)) {}
value( string&& v): m_Type(construct(m_Storage, std::move(v))) {}
value(const string& v): m_Type(construct(m_Storage, v)) {}
value(const char* v): m_Type(construct(m_Storage, v)) {}
value( boolean v): m_Type(construct(m_Storage, v)) {}
value( number v): m_Type(construct(m_Storage, v)) {}
~value() { destruct(m_Storage, m_Type); }
value& operator=(value&& other) { if (this != &other) { value(std::move(other)).swap(*this); } return *this; }
value& operator=(const value& other) { if (this != &other) { value( other).swap(*this); } return *this; }
value& operator=( null) { auto other = value( ); swap(other); return *this; }
value& operator=( object&& v) { auto other = value(std::move(v)); swap(other); return *this; }
value& operator=(const object& v) { auto other = value( v); swap(other); return *this; }
value& operator=( array&& v) { auto other = value(std::move(v)); swap(other); return *this; }
value& operator=(const array& v) { auto other = value( v); swap(other); return *this; }
value& operator=( string&& v) { auto other = value(std::move(v)); swap(other); return *this; }
value& operator=(const string& v) { auto other = value( v); swap(other); return *this; }
value& operator=(const char* v) { auto other = value( v); swap(other); return *this; }
value& operator=( boolean v) { auto other = value( v); swap(other); return *this; }
value& operator=( number v) { auto other = value( v); swap(other); return *this; }
type_t type() const { return m_Type; }
operator type_t() const { return m_Type; }
value& operator[](size_t index);
const value& operator[](size_t index) const;
value& operator[](const string& key);
const value& operator[](const string& key) const;
bool contains(const string& key) const;
void push_back(const value& value);
void push_back(value&& value);
bool is_primitive() const { return is_string() || is_number() || is_boolean() || is_null(); }
bool is_structured() const { return is_object() || is_array(); }
bool is_null() const { return m_Type == type_t::null; }
bool is_object() const { return m_Type == type_t::object; }
bool is_array() const { return m_Type == type_t::array; }
bool is_string() const { return m_Type == type_t::string; }
bool is_boolean() const { return m_Type == type_t::boolean; }
bool is_number() const { return m_Type == type_t::number; }
bool is_discarded() const { return m_Type == type_t::discarded; }
template <typename T> const T& get() const;
template <typename T> T& get();
string dump(const int indent = -1, const char indent_char = ' ') const;
void swap(value& other);
inline friend void swap(value& lhs, value& rhs) { lhs.swap(rhs); }
// Returns discarded value for invalid inputs.
static value parse(const string& data);
private:
struct parser;
// VS2015: std::max() is not constexpr yet.
# define CRUDE_MAX2(a, b) ((a) < (b) ? (b) : (a))
# define CRUDE_MAX3(a, b, c) CRUDE_MAX2(CRUDE_MAX2(a, b), c)
# define CRUDE_MAX4(a, b, c, d) CRUDE_MAX2(CRUDE_MAX3(a, b, c), d)
# define CRUDE_MAX5(a, b, c, d, e) CRUDE_MAX2(CRUDE_MAX4(a, b, c, d), e)
enum
{
max_size = CRUDE_MAX5( sizeof(string), sizeof(object), sizeof(array), sizeof(number), sizeof(boolean)),
max_align = CRUDE_MAX5(alignof(string), alignof(object), alignof(array), alignof(number), alignof(boolean))
};
# undef CRUDE_MAX5
# undef CRUDE_MAX4
# undef CRUDE_MAX3
# undef CRUDE_MAX2
using storage_t = std::aligned_storage<max_size, max_align>::type;
static object* object_ptr( storage_t& storage) { return reinterpret_cast< object*>(&storage); }
static const object* object_ptr(const storage_t& storage) { return reinterpret_cast<const object*>(&storage); }
static array* array_ptr( storage_t& storage) { return reinterpret_cast< array*>(&storage); }
static const array* array_ptr(const storage_t& storage) { return reinterpret_cast<const array*>(&storage); }
static string* string_ptr( storage_t& storage) { return reinterpret_cast< string*>(&storage); }
static const string* string_ptr(const storage_t& storage) { return reinterpret_cast<const string*>(&storage); }
static boolean* boolean_ptr( storage_t& storage) { return reinterpret_cast< boolean*>(&storage); }
static const boolean* boolean_ptr(const storage_t& storage) { return reinterpret_cast<const boolean*>(&storage); }
static number* number_ptr( storage_t& storage) { return reinterpret_cast< number*>(&storage); }
static const number* number_ptr(const storage_t& storage) { return reinterpret_cast<const number*>(&storage); }
static type_t construct(storage_t& storage, type_t type)
{
switch (type)
{
case type_t::object: new (&storage) object(); break;
case type_t::array: new (&storage) array(); break;
case type_t::string: new (&storage) string(); break;
case type_t::boolean: new (&storage) boolean(); break;
case type_t::number: new (&storage) number(); break;
default: break;
}
return type;
}
static type_t construct(storage_t& storage, null) { return type_t::null; }
static type_t construct(storage_t& storage, object&& value) { new (&storage) object(std::forward<object>(value)); return type_t::object; }
static type_t construct(storage_t& storage, const object& value) { new (&storage) object(value); return type_t::object; }
static type_t construct(storage_t& storage, array&& value) { new (&storage) array(std::forward<array>(value)); return type_t::array; }
static type_t construct(storage_t& storage, const array& value) { new (&storage) array(value); return type_t::array; }
static type_t construct(storage_t& storage, string&& value) { new (&storage) string(std::forward<string>(value)); return type_t::string; }
static type_t construct(storage_t& storage, const string& value) { new (&storage) string(value); return type_t::string; }
static type_t construct(storage_t& storage, const char* value) { new (&storage) string(value); return type_t::string; }
static type_t construct(storage_t& storage, boolean value) { new (&storage) boolean(value); return type_t::boolean; }
static type_t construct(storage_t& storage, number value) { new (&storage) number(value); return type_t::number; }
static void destruct(storage_t& storage, type_t type)
{
switch (type)
{
case type_t::object: object_ptr(storage)->~object(); break;
case type_t::array: array_ptr(storage)->~array(); break;
case type_t::string: string_ptr(storage)->~string(); break;
default: break;
}
}
struct dump_context_t
{
std::ostringstream out;
const int indent = -1;
const char indent_char = ' ';
// VS2015: Aggregate initialization isn't a thing yet.
dump_context_t(const int indent, const char indent_char)
: indent(indent)
, indent_char(indent_char)
{
}
void write_indent(int level);
void write_separator();
void write_newline();
};
void dump(dump_context_t& context, int level) const;
storage_t m_Storage;
type_t m_Type;
};
template <> inline const object& value::get<object>() const { CRUDE_ASSERT(m_Type == type_t::object); return *object_ptr(m_Storage); }
template <> inline const array& value::get<array>() const { CRUDE_ASSERT(m_Type == type_t::array); return *array_ptr(m_Storage); }
template <> inline const string& value::get<string>() const { CRUDE_ASSERT(m_Type == type_t::string); return *string_ptr(m_Storage); }
template <> inline const boolean& value::get<boolean>() const { CRUDE_ASSERT(m_Type == type_t::boolean); return *boolean_ptr(m_Storage); }
template <> inline const number& value::get<number>() const { CRUDE_ASSERT(m_Type == type_t::number); return *number_ptr(m_Storage); }
template <> inline object& value::get<object>() { CRUDE_ASSERT(m_Type == type_t::object); return *object_ptr(m_Storage); }
template <> inline array& value::get<array>() { CRUDE_ASSERT(m_Type == type_t::array); return *array_ptr(m_Storage); }
template <> inline string& value::get<string>() { CRUDE_ASSERT(m_Type == type_t::string); return *string_ptr(m_Storage); }
template <> inline boolean& value::get<boolean>() { CRUDE_ASSERT(m_Type == type_t::boolean); return *boolean_ptr(m_Storage); }
template <> inline number& value::get<number>() { CRUDE_ASSERT(m_Type == type_t::number); return *number_ptr(m_Storage); }
} // namespace crude_json
# endif // __CRUDE_JSON_H__
@@ -0,0 +1,142 @@
//------------------------------------------------------------------------------
// LICENSE
// This software is dual-licensed to the public domain and under the following
// license: you are granted a perpetual, irrevocable license to copy, modify,
// publish, and distribute this file as you see fit.
//
// CREDITS
// Written by Michal Cichon
//------------------------------------------------------------------------------
# ifndef __IMGUI_BEZIER_MATH_H__
# define __IMGUI_BEZIER_MATH_H__
# pragma once
//------------------------------------------------------------------------------
# include "imgui_extra_math.h"
//------------------------------------------------------------------------------
template <typename T>
struct ImCubicBezierPointsT
{
T P0;
T P1;
T P2;
T P3;
};
using ImCubicBezierPoints = ImCubicBezierPointsT<ImVec2>;
//------------------------------------------------------------------------------
// Low-level Bezier curve sampling.
template <typename T> inline T ImLinearBezier(const T& p0, const T& p1, float t);
template <typename T> inline T ImLinearBezierDt(const T& p0, const T& p1, float t);
template <typename T> inline T ImQuadraticBezier(const T& p0, const T& p1, const T& p2, float t);
template <typename T> inline T ImQuadraticBezierDt(const T& p0, const T& p1, const T& p2, float t);
template <typename T> inline T ImCubicBezier(const T& p0, const T& p1, const T& p2, const T& p3, float t);
template <typename T> inline T ImCubicBezierDt(const T& p0, const T& p1, const T& p2, const T& p3, float t);
// High-level Bezier sampling, automatically collapse to lower level Bezier curves if control points overlap.
template <typename T> inline T ImCubicBezierSample(const T& p0, const T& p1, const T& p2, const T& p3, float t);
template <typename T> inline T ImCubicBezierSample(const ImCubicBezierPointsT<T>& curve, float t);
template <typename T> inline T ImCubicBezierTangent(const T& p0, const T& p1, const T& p2, const T& p3, float t);
template <typename T> inline T ImCubicBezierTangent(const ImCubicBezierPointsT<T>& curve, float t);
// Calculate approximate length of Cubic Bezier curve.
template <typename T> inline float ImCubicBezierLength(const T& p0, const T& p1, const T& p2, const T& p3);
template <typename T> inline float ImCubicBezierLength(const ImCubicBezierPointsT<T>& curve);
// Splits Cubic Bezier curve into two curves.
template <typename T>
struct ImCubicBezierSplitResultT
{
ImCubicBezierPointsT<T> Left;
ImCubicBezierPointsT<T> Right;
};
using ImCubicBezierSplitResult = ImCubicBezierSplitResultT<ImVec2>;
template <typename T> inline ImCubicBezierSplitResultT<T> ImCubicBezierSplit(const T& p0, const T& p1, const T& p2, const T& p3, float t);
template <typename T> inline ImCubicBezierSplitResultT<T> ImCubicBezierSplit(const ImCubicBezierPointsT<T>& curve, float t);
// Returns bounding rectangle of Cubic Bezier curve.
inline ImRect ImCubicBezierBoundingRect(const ImVec2& p0, const ImVec2& p1, const ImVec2& p2, const ImVec2& p3);
inline ImRect ImCubicBezierBoundingRect(const ImCubicBezierPoints& curve);
// Project point on Cubic Bezier curve.
struct ImProjectResult
{
ImVec2 Point; // Point on curve
float Time; // [0 - 1]
float Distance; // Distance to curve
};
inline ImProjectResult ImProjectOnCubicBezier(const ImVec2& p, const ImVec2& p0, const ImVec2& p1, const ImVec2& p2, const ImVec2& p3, const int subdivisions = 100);
inline ImProjectResult ImProjectOnCubicBezier(const ImVec2& p, const ImCubicBezierPoints& curve, const int subdivisions = 100);
// Calculate intersection between line and a Cubic Bezier curve.
struct ImCubicBezierIntersectResult
{
int Count;
ImVec2 Points[3];
};
inline ImCubicBezierIntersectResult ImCubicBezierLineIntersect(const ImVec2& p0, const ImVec2& p1, const ImVec2& p2, const ImVec2& p3, const ImVec2& a0, const ImVec2& a1);
inline ImCubicBezierIntersectResult ImCubicBezierLineIntersect(const ImCubicBezierPoints& curve, const ImLine& line);
// Adaptive Cubic Bezier subdivision.
enum ImCubicBezierSubdivideFlags
{
ImCubicBezierSubdivide_None = 0,
ImCubicBezierSubdivide_SkipFirst = 1
};
struct ImCubicBezierSubdivideSample
{
ImVec2 Point;
ImVec2 Tangent;
};
using ImCubicBezierSubdivideCallback = void (*)(const ImCubicBezierSubdivideSample& p, void* user_pointer);
inline void ImCubicBezierSubdivide(ImCubicBezierSubdivideCallback callback, void* user_pointer, const ImVec2& p0, const ImVec2& p1, const ImVec2& p2, const ImVec2& p3, float tess_tol = -1.0f, ImCubicBezierSubdivideFlags flags = ImCubicBezierSubdivide_None);
inline void ImCubicBezierSubdivide(ImCubicBezierSubdivideCallback callback, void* user_pointer, const ImCubicBezierPoints& curve, float tess_tol = -1.0f, ImCubicBezierSubdivideFlags flags = ImCubicBezierSubdivide_None);
// F has signature void(const ImCubicBezierSubdivideSample& p)
template <typename F> inline void ImCubicBezierSubdivide(F& callback, const ImVec2& p0, const ImVec2& p1, const ImVec2& p2, const ImVec2& p3, float tess_tol = -1.0f, ImCubicBezierSubdivideFlags flags = ImCubicBezierSubdivide_None);
template <typename F> inline void ImCubicBezierSubdivide(F& callback, const ImCubicBezierPoints& curve, float tess_tol = -1.0f, ImCubicBezierSubdivideFlags flags = ImCubicBezierSubdivide_None);
// Fixed step Cubic Bezier subdivision.
struct ImCubicBezierFixedStepSample
{
float T;
float Length;
ImVec2 Point;
bool BreakSearch;
};
using ImCubicBezierFixedStepCallback = void (*)(ImCubicBezierFixedStepSample& sample, void* user_pointer);
inline void ImCubicBezierFixedStep(ImCubicBezierFixedStepCallback callback, void* user_pointer, const ImVec2& p0, const ImVec2& p1, const ImVec2& p2, const ImVec2& p3, float step, bool overshoot = false, float max_value_error = 1e-3f, float max_t_error = 1e-5f);
inline void ImCubicBezierFixedStep(ImCubicBezierFixedStepCallback callback, void* user_pointer, const ImCubicBezierPoints& curve, float step, bool overshoot = false, float max_value_error = 1e-3f, float max_t_error = 1e-5f);
// F has signature void(const ImCubicBezierFixedStepSample& p)
template <typename F> inline void ImCubicBezierFixedStep(F& callback, const ImVec2& p0, const ImVec2& p1, const ImVec2& p2, const ImVec2& p3, float step, bool overshoot = false, float max_value_error = 1e-3f, float max_t_error = 1e-5f);
template <typename F> inline void ImCubicBezierFixedStep(F& callback, const ImCubicBezierPoints& curve, float step, bool overshoot = false, float max_value_error = 1e-3f, float max_t_error = 1e-5f);
//------------------------------------------------------------------------------
# include "imgui_bezier_math.inl"
//------------------------------------------------------------------------------
# endif // __IMGUI_BEZIER_MATH_H__
@@ -0,0 +1,668 @@
//------------------------------------------------------------------------------
// LICENSE
// This software is dual-licensed to the public domain and under the following
// license: you are granted a perpetual, irrevocable license to copy, modify,
// publish, and distribute this file as you see fit.
//
// CREDITS
// Written by Michal Cichon
//------------------------------------------------------------------------------
# ifndef __IMGUI_BEZIER_MATH_INL__
# define __IMGUI_BEZIER_MATH_INL__
# pragma once
//------------------------------------------------------------------------------
# include "imgui_bezier_math.h"
# include <map> // used in ImCubicBezierFixedStep
//------------------------------------------------------------------------------
template <typename T>
inline T ImLinearBezier(const T& p0, const T& p1, float t)
{
return p0 + t * (p1 - p0);
}
template <typename T>
inline T ImLinearBezierDt(const T& p0, const T& p1, float t)
{
return p1 - p0;
}
template <typename T>
inline T ImQuadraticBezier(const T& p0, const T& p1, const T& p2, float t)
{
const auto a = 1 - t;
return a * a * p0 + 2 * t * a * p1 + t * t * p2;
}
template <typename T>
inline T ImQuadraticBezierDt(const T& p0, const T& p1, const T& p2, float t)
{
return 2 * (1 - t) * (p1 - p0) + 2 * t * (p2 - p1);
}
template <typename T>
inline T ImCubicBezier(const T& p0, const T& p1, const T& p2, const T& p3, float t)
{
const auto a = 1 - t;
const auto b = a * a * a;
const auto c = t * t * t;
return b * p0 + 3 * t * a * a * p1 + 3 * t * t * a * p2 + c * p3;
}
template <typename T>
inline T ImCubicBezierDt(const T& p0, const T& p1, const T& p2, const T& p3, float t)
{
const auto a = 1 - t;
const auto b = a * a;
const auto c = t * t;
const auto d = 2 * t * a;
return -3 * p0 * b + 3 * p1 * (b - d) + 3 * p2 * (d - c) + 3 * p3 * c;
}
template <typename T>
inline T ImCubicBezierSample(const T& p0, const T& p1, const T& p2, const T& p3, float t)
{
const auto cp0_zero = ImLengthSqr(p1 - p0) < 1e-5f;
const auto cp1_zero = ImLengthSqr(p3 - p2) < 1e-5f;
if (cp0_zero && cp1_zero)
return ImLinearBezier(p0, p3, t);
else if (cp0_zero)
return ImQuadraticBezier(p0, p2, p3, t);
else if (cp1_zero)
return ImQuadraticBezier(p0, p1, p3, t);
else
return ImCubicBezier(p0, p1, p2, p3, t);
}
template <typename T>
inline T ImCubicBezierSample(const ImCubicBezierPointsT<T>& curve, float t)
{
return ImCubicBezierSample(curve.P0, curve.P1, curve.P2, curve.P3, t);
}
template <typename T>
inline T ImCubicBezierTangent(const T& p0, const T& p1, const T& p2, const T& p3, float t)
{
const auto cp0_zero = ImLengthSqr(p1 - p0) < 1e-5f;
const auto cp1_zero = ImLengthSqr(p3 - p2) < 1e-5f;
if (cp0_zero && cp1_zero)
return ImLinearBezierDt(p0, p3, t);
else if (cp0_zero)
return ImQuadraticBezierDt(p0, p2, p3, t);
else if (cp1_zero)
return ImQuadraticBezierDt(p0, p1, p3, t);
else
return ImCubicBezierDt(p0, p1, p2, p3, t);
}
template <typename T>
inline T ImCubicBezierTangent(const ImCubicBezierPointsT<T>& curve, float t)
{
return ImCubicBezierTangent(curve.P0, curve.P1, curve.P2, curve.P3, t);
}
template <typename T>
inline float ImCubicBezierLength(const T& p0, const T& p1, const T& p2, const T& p3)
{
// Legendre-Gauss abscissae with n=24 (x_i values, defined at i=n as the roots of the nth order Legendre polynomial Pn(x))
static const float t_values[] =
{
-0.0640568928626056260850430826247450385909f,
0.0640568928626056260850430826247450385909f,
-0.1911188674736163091586398207570696318404f,
0.1911188674736163091586398207570696318404f,
-0.3150426796961633743867932913198102407864f,
0.3150426796961633743867932913198102407864f,
-0.4337935076260451384870842319133497124524f,
0.4337935076260451384870842319133497124524f,
-0.5454214713888395356583756172183723700107f,
0.5454214713888395356583756172183723700107f,
-0.6480936519369755692524957869107476266696f,
0.6480936519369755692524957869107476266696f,
-0.7401241915785543642438281030999784255232f,
0.7401241915785543642438281030999784255232f,
-0.8200019859739029219539498726697452080761f,
0.8200019859739029219539498726697452080761f,
-0.8864155270044010342131543419821967550873f,
0.8864155270044010342131543419821967550873f,
-0.9382745520027327585236490017087214496548f,
0.9382745520027327585236490017087214496548f,
-0.9747285559713094981983919930081690617411f,
0.9747285559713094981983919930081690617411f,
-0.9951872199970213601799974097007368118745f,
0.9951872199970213601799974097007368118745f
};
// Legendre-Gauss weights with n=24 (w_i values, defined by a function linked to in the Bezier primer article)
static const float c_values[] =
{
0.1279381953467521569740561652246953718517f,
0.1279381953467521569740561652246953718517f,
0.1258374563468282961213753825111836887264f,
0.1258374563468282961213753825111836887264f,
0.1216704729278033912044631534762624256070f,
0.1216704729278033912044631534762624256070f,
0.1155056680537256013533444839067835598622f,
0.1155056680537256013533444839067835598622f,
0.1074442701159656347825773424466062227946f,
0.1074442701159656347825773424466062227946f,
0.0976186521041138882698806644642471544279f,
0.0976186521041138882698806644642471544279f,
0.0861901615319532759171852029837426671850f,
0.0861901615319532759171852029837426671850f,
0.0733464814110803057340336152531165181193f,
0.0733464814110803057340336152531165181193f,
0.0592985849154367807463677585001085845412f,
0.0592985849154367807463677585001085845412f,
0.0442774388174198061686027482113382288593f,
0.0442774388174198061686027482113382288593f,
0.0285313886289336631813078159518782864491f,
0.0285313886289336631813078159518782864491f,
0.0123412297999871995468056670700372915759f,
0.0123412297999871995468056670700372915759f
};
static_assert(sizeof(t_values) / sizeof(*t_values) == sizeof(c_values) / sizeof(*c_values), "");
auto arc = [p0, p1, p2, p3](float t)
{
const auto p = ImCubicBezierDt(p0, p1, p2, p3, t);
const auto l = ImLength(p);
return l;
};
const auto z = 0.5f;
const auto n = sizeof(t_values) / sizeof(*t_values);
auto accumulator = 0.0f;
for (size_t i = 0; i < n; ++i)
{
const auto t = z * t_values[i] + z;
accumulator += c_values[i] * arc(t);
}
return z * accumulator;
}
template <typename T>
inline float ImCubicBezierLength(const ImCubicBezierPointsT<T>& curve)
{
return ImCubicBezierLength(curve.P0, curve.P1, curve.P2, curve.P3);
}
template <typename T>
inline ImCubicBezierSplitResultT<T> ImCubicBezierSplit(const T& p0, const T& p1, const T& p2, const T& p3, float t)
{
const auto z1 = t;
const auto z2 = z1 * z1;
const auto z3 = z1 * z1 * z1;
const auto s1 = z1 - 1;
const auto s2 = s1 * s1;
const auto s3 = s1 * s1 * s1;
return ImCubicBezierSplitResultT<T>
{
ImCubicBezierPointsT<T>
{
p0,
z1 * p1 - s1 * p0,
z2 * p2 - 2 * z1 * s1 * p1 + s2 * p0,
z3 * p3 - 3 * z2 * s1 * p2 + 3 * z1 * s2 * p1 - s3 * p0
},
ImCubicBezierPointsT<T>
{
z3 * p0 - 3 * z2 * s1 * p1 + 3 * z1 * s2 * p2 - s3 * p3,
z2 * p1 - 2 * z1 * s1 * p2 + s2 * p3,
z1 * p2 - s1 * p3,
p3,
}
};
}
template <typename T>
inline ImCubicBezierSplitResultT<T> ImCubicBezierSplit(const ImCubicBezierPointsT<T>& curve, float t)
{
return ImCubicBezierSplit(curve.P0, curve.P1, curve.P2, curve.P3, t);
}
inline ImRect ImCubicBezierBoundingRect(const ImVec2& p0, const ImVec2& p1, const ImVec2& p2, const ImVec2& p3)
{
auto a = 3 * p3 - 9 * p2 + 9 * p1 - 3 * p0;
auto b = 6 * p0 - 12 * p1 + 6 * p2;
auto c = 3 * p1 - 3 * p0;
auto delta_squared = ImMul(b, b) - 4 * ImMul(a, c);
auto tl = ImMin(p0, p3);
auto rb = ImMax(p0, p3);
# define IM_VEC2_INDEX(v, i) *(&v.x + i)
for (int i = 0; i < 2; ++i)
{
if (IM_VEC2_INDEX(delta_squared, i) >= 0)
{
auto delta = ImSqrt(IM_VEC2_INDEX(delta_squared, i));
auto t0 = (-IM_VEC2_INDEX(b, i) + delta) / (2 * IM_VEC2_INDEX(a, i));
if (t0 > 0 && t0 < 1)
{
auto p = ImCubicBezier(IM_VEC2_INDEX(p0, i), IM_VEC2_INDEX(p1, i), IM_VEC2_INDEX(p2, i), IM_VEC2_INDEX(p3, i), t0);
IM_VEC2_INDEX(tl, i) = ImMin(IM_VEC2_INDEX(tl, i), p);
IM_VEC2_INDEX(rb, i) = ImMax(IM_VEC2_INDEX(rb, i), p);
}
auto t1 = (-IM_VEC2_INDEX(b, i) - delta) / (2 * IM_VEC2_INDEX(a, i));
if (t1 > 0 && t1 < 1)
{
auto p = ImCubicBezier(IM_VEC2_INDEX(p0, i), IM_VEC2_INDEX(p1, i), IM_VEC2_INDEX(p2, i), IM_VEC2_INDEX(p3, i), t1);
IM_VEC2_INDEX(tl, i) = ImMin(IM_VEC2_INDEX(tl, i), p);
IM_VEC2_INDEX(rb, i) = ImMax(IM_VEC2_INDEX(rb, i), p);
}
}
}
# undef IM_VEC2_INDEX
return ImRect(tl, rb);
}
inline ImRect ImCubicBezierBoundingRect(const ImCubicBezierPoints& curve)
{
return ImCubicBezierBoundingRect(curve.P0, curve.P1, curve.P2, curve.P3);
}
inline ImProjectResult ImProjectOnCubicBezier(const ImVec2& point, const ImVec2& p0, const ImVec2& p1, const ImVec2& p2, const ImVec2& p3, const int subdivisions)
{
// http://pomax.github.io/bezierinfo/#projections
const float epsilon = 1e-5f;
const float fixed_step = 1.0f / static_cast<float>(subdivisions - 1);
ImProjectResult result;
result.Point = point;
result.Time = 0.0f;
result.Distance = FLT_MAX;
// Step 1: Coarse check
for (int i = 0; i < subdivisions; ++i)
{
auto t = i * fixed_step;
auto p = ImCubicBezier(p0, p1, p2, p3, t);
auto s = point - p;
auto d = ImDot(s, s);
if (d < result.Distance)
{
result.Point = p;
result.Time = t;
result.Distance = d;
}
}
if (result.Time == 0.0f || ImFabs(result.Time - 1.0f) <= epsilon)
{
result.Distance = ImSqrt(result.Distance);
return result;
}
// Step 2: Fine check
auto left = result.Time - fixed_step;
auto right = result.Time + fixed_step;
auto step = fixed_step * 0.1f;
for (auto t = left; t < right + step; t += step)
{
auto p = ImCubicBezier(p0, p1, p2, p3, t);
auto s = point - p;
auto d = ImDot(s, s);
if (d < result.Distance)
{
result.Point = p;
result.Time = t;
result.Distance = d;
}
}
result.Distance = ImSqrt(result.Distance);
return result;
}
inline ImProjectResult ImProjectOnCubicBezier(const ImVec2& p, const ImCubicBezierPoints& curve, const int subdivisions)
{
return ImProjectOnCubicBezier(p, curve.P0, curve.P1, curve.P2, curve.P3, subdivisions);
}
inline ImCubicBezierIntersectResult ImCubicBezierLineIntersect(const ImVec2& p0, const ImVec2& p1, const ImVec2& p2, const ImVec2& p3, const ImVec2& a0, const ImVec2& a1)
{
auto cubic_roots = [](float a, float b, float c, float d, float* roots) -> int
{
int count = 0;
auto sign = [](float x) -> float { return x < 0 ? -1.0f : 1.0f; };
auto A = b / a;
auto B = c / a;
auto C = d / a;
auto Q = (3 * B - ImPow(A, 2)) / 9;
auto R = (9 * A * B - 27 * C - 2 * ImPow(A, 3)) / 54;
auto D = ImPow(Q, 3) + ImPow(R, 2); // polynomial discriminant
if (D >= 0) // complex or duplicate roots
{
auto S = sign(R + ImSqrt(D)) * ImPow(ImFabs(R + ImSqrt(D)), (1.0f / 3.0f));
auto T = sign(R - ImSqrt(D)) * ImPow(ImFabs(R - ImSqrt(D)), (1.0f / 3.0f));
roots[0] = -A / 3 + (S + T); // real root
roots[1] = -A / 3 - (S + T) / 2; // real part of complex root
roots[2] = -A / 3 - (S + T) / 2; // real part of complex root
auto Im = ImFabs(ImSqrt(3) * (S - T) / 2); // complex part of root pair
// discard complex roots
if (Im != 0)
count = 1;
else
count = 3;
}
else // distinct real roots
{
auto th = ImAcos(R / ImSqrt(-ImPow(Q, 3)));
roots[0] = 2 * ImSqrt(-Q) * ImCos(th / 3) - A / 3;
roots[1] = 2 * ImSqrt(-Q) * ImCos((th + 2 * IM_PI) / 3) - A / 3;
roots[2] = 2 * ImSqrt(-Q) * ImCos((th + 4 * IM_PI) / 3) - A / 3;
count = 3;
}
return count;
};
// https://github.com/kaishiqi/Geometric-Bezier/blob/master/GeometricBezier/src/kaishiqi/geometric/intersection/Intersection.as
//
// Start with Bezier using Bernstein polynomials for weighting functions:
// (1-t^3)P0 + 3t(1-t)^2P1 + 3t^2(1-t)P2 + t^3P3
//
// Expand and collect terms to form linear combinations of original Bezier
// controls. This ends up with a vector cubic in t:
// (-P0+3P1-3P2+P3)t^3 + (3P0-6P1+3P2)t^2 + (-3P0+3P1)t + P0
// /\ /\ /\ /\
// || || || ||
// c3 c2 c1 c0
// Calculate the coefficients
auto c3 = -p0 + 3 * p1 - 3 * p2 + p3;
auto c2 = 3 * p0 - 6 * p1 + 3 * p2;
auto c1 = -3 * p0 + 3 * p1;
auto c0 = p0;
// Convert line to normal form: ax + by + c = 0
auto a = a1.y - a0.y;
auto b = a0.x - a1.x;
auto c = a0.x * (a0.y - a1.y) + a0.y * (a1.x - a0.x);
// Rotate each cubic coefficient using line for new coordinate system?
// Find roots of rotated cubic
float roots[3];
auto rootCount = cubic_roots(
a * c3.x + b * c3.y,
a * c2.x + b * c2.y,
a * c1.x + b * c1.y,
a * c0.x + b * c0.y + c,
roots);
// Any roots in closed interval [0,1] are intersections on Bezier, but
// might not be on the line segment.
// Find intersections and calculate point coordinates
auto min = ImMin(a0, a1);
auto max = ImMax(a0, a1);
ImCubicBezierIntersectResult result;
auto points = result.Points;
for (int i = 0; i < rootCount; ++i)
{
auto root = roots[i];
if (0 <= root && root <= 1)
{
// We're within the Bezier curve
// Find point on Bezier
auto p = ImCubicBezier(p0, p1, p2, p3, root);
// See if point is on line segment
// Had to make special cases for vertical and horizontal lines due
// to slight errors in calculation of p00
if (a0.x == a1.x)
{
if (min.y <= p.y && p.y <= max.y)
*points++ = p;
}
else if (a0.y == a1.y)
{
if (min.x <= p.x && p.x <= max.x)
*points++ = p;
}
else if (p.x >= min.x && p.y >= min.y && p.x <= max.x && p.y <= max.y)
{
*points++ = p;
}
}
}
result.Count = static_cast<int>(points - result.Points);
return result;
}
inline ImCubicBezierIntersectResult ImCubicBezierLineIntersect(const ImCubicBezierPoints& curve, const ImLine& line)
{
return ImCubicBezierLineIntersect(curve.P0, curve.P1, curve.P2, curve.P3, line.A, line.B);
}
inline void ImCubicBezierSubdivide(ImCubicBezierSubdivideCallback callback, void* user_pointer, const ImVec2& p0, const ImVec2& p1, const ImVec2& p2, const ImVec2& p3, float tess_tol, ImCubicBezierSubdivideFlags flags)
{
return ImCubicBezierSubdivide(callback, user_pointer, ImCubicBezierPoints{ p0, p1, p2, p3 }, tess_tol, flags);
}
inline void ImCubicBezierSubdivide(ImCubicBezierSubdivideCallback callback, void* user_pointer, const ImCubicBezierPoints& curve, float tess_tol, ImCubicBezierSubdivideFlags flags)
{
struct Tesselator
{
ImCubicBezierSubdivideCallback Callback;
void* UserPointer;
float TesselationTollerance;
ImCubicBezierSubdivideFlags Flags;
void Commit(const ImVec2& p, const ImVec2& t)
{
ImCubicBezierSubdivideSample sample;
sample.Point = p;
sample.Tangent = t;
Callback(sample, UserPointer);
}
void Subdivide(const ImCubicBezierPoints& curve, int level = 0)
{
float dx = curve.P3.x - curve.P0.x;
float dy = curve.P3.y - curve.P0.y;
float d2 = ((curve.P1.x - curve.P3.x) * dy - (curve.P1.y - curve.P3.y) * dx);
float d3 = ((curve.P2.x - curve.P3.x) * dy - (curve.P2.y - curve.P3.y) * dx);
d2 = (d2 >= 0) ? d2 : -d2;
d3 = (d3 >= 0) ? d3 : -d3;
if ((d2 + d3) * (d2 + d3) < TesselationTollerance * (dx * dx + dy * dy))
{
Commit(curve.P3, ImCubicBezierTangent(curve, 1.0f));
}
else if (level < 10)
{
const auto p12 = (curve.P0 + curve.P1) * 0.5f;
const auto p23 = (curve.P1 + curve.P2) * 0.5f;
const auto p34 = (curve.P2 + curve.P3) * 0.5f;
const auto p123 = (p12 + p23) * 0.5f;
const auto p234 = (p23 + p34) * 0.5f;
const auto p1234 = (p123 + p234) * 0.5f;
Subdivide(ImCubicBezierPoints { curve.P0, p12, p123, p1234 }, level + 1);
Subdivide(ImCubicBezierPoints { p1234, p234, p34, curve.P3 }, level + 1);
}
}
};
if (tess_tol < 0)
tess_tol = 1.118f; // sqrtf(1.25f)
Tesselator tesselator;
tesselator.Callback = callback;
tesselator.UserPointer = user_pointer;
tesselator.TesselationTollerance = tess_tol * tess_tol;
tesselator.Flags = flags;
if (!(tesselator.Flags & ImCubicBezierSubdivide_SkipFirst))
tesselator.Commit(curve.P0, ImCubicBezierTangent(curve, 0.0f));
tesselator.Subdivide(curve, 0);
}
template <typename F> inline void ImCubicBezierSubdivide(F& callback, const ImVec2& p0, const ImVec2& p1, const ImVec2& p2, const ImVec2& p3, float tess_tol, ImCubicBezierSubdivideFlags flags)
{
auto handler = [](const ImCubicBezierSubdivideSample& p, void* user_pointer)
{
auto& callback = *reinterpret_cast<F*>(user_pointer);
callback(p);
};
ImCubicBezierSubdivide(handler, &callback, ImCubicBezierPoints{ p0, p1, p2, p3 }, tess_tol, flags);
}
template <typename F> inline void ImCubicBezierSubdivide(F& callback, const ImCubicBezierPoints& curve, float tess_tol, ImCubicBezierSubdivideFlags flags)
{
auto handler = [](const ImCubicBezierSubdivideSample& p, void* user_pointer)
{
auto& callback = *reinterpret_cast<F*>(user_pointer);
callback(p);
};
ImCubicBezierSubdivide(handler, &callback, curve, tess_tol, flags);
}
inline void ImCubicBezierFixedStep(ImCubicBezierFixedStepCallback callback, void* user_pointer, const ImVec2& p0, const ImVec2& p1, const ImVec2& p2, const ImVec2& p3, float step, bool overshoot, float max_value_error, float max_t_error)
{
if (step <= 0.0f || !callback || max_value_error <= 0 || max_t_error <= 0)
return;
ImCubicBezierFixedStepSample sample;
sample.T = 0.0f;
sample.Length = 0.0f;
sample.Point = p0;
sample.BreakSearch = false;
callback(sample, user_pointer);
if (sample.BreakSearch)
return;
const auto length = ImCubicBezierLength(p0, p1, p2, p3);
const auto point_count = static_cast<int>(length / step) + (overshoot ? 2 : 1);
const auto t_min = 0.0f;
const auto t_max = step * point_count / length;
const auto t_0 = (t_min + t_max) * 0.5f;
// #todo: replace map with ImVector + binary search
std::map<float, float> cache;
for (int point_index = 1; point_index < point_count; ++point_index)
{
const auto targetLength = point_index * step;
float t_start = t_min;
float t_end = t_max;
float t = t_0;
float t_best = t;
float error_best = length;
while (true)
{
auto cacheIt = cache.find(t);
if (cacheIt == cache.end())
{
const auto front = ImCubicBezierSplit(p0, p1, p2, p3, t).Left;
const auto length = ImCubicBezierLength(front);
cacheIt = cache.emplace(t, length).first;
}
const auto length = cacheIt->second;
const auto error = targetLength - length;
if (error < error_best)
{
error_best = error;
t_best = t;
}
if (ImFabs(error) <= max_value_error || ImFabs(t_start - t_end) <= max_t_error)
{
sample.T = t;
sample.Length = length;
sample.Point = ImCubicBezier(p0, p1, p2, p3, t);
callback(sample, user_pointer);
if (sample.BreakSearch)
return;
break;
}
else if (error < 0.0f)
t_end = t;
else // if (error > 0.0f)
t_start = t;
t = (t_start + t_end) * 0.5f;
}
}
}
inline void ImCubicBezierFixedStep(ImCubicBezierFixedStepCallback callback, void* user_pointer, const ImCubicBezierPoints& curve, float step, bool overshoot, float max_value_error, float max_t_error)
{
ImCubicBezierFixedStep(callback, user_pointer, curve.P0, curve.P1, curve.P2, curve.P3, step, overshoot, max_value_error, max_t_error);
}
// F has signature void(const ImCubicBezierFixedStepSample& p)
template <typename F>
inline void ImCubicBezierFixedStep(F& callback, const ImVec2& p0, const ImVec2& p1, const ImVec2& p2, const ImVec2& p3, float step, bool overshoot, float max_value_error, float max_t_error)
{
auto handler = [](ImCubicBezierFixedStepSample& sample, void* user_pointer)
{
auto& callback = *reinterpret_cast<F*>(user_pointer);
callback(sample);
};
ImCubicBezierFixedStep(handler, &callback, p0, p1, p2, p3, step, overshoot, max_value_error, max_t_error);
}
template <typename F>
inline void ImCubicBezierFixedStep(F& callback, const ImCubicBezierPoints& curve, float step, bool overshoot, float max_value_error, float max_t_error)
{
auto handler = [](ImCubicBezierFixedStepSample& sample, void* user_pointer)
{
auto& callback = *reinterpret_cast<F*>(user_pointer);
callback(sample);
};
ImCubicBezierFixedStep(handler, &callback, curve.P0, curve.P1, curve.P2, curve.P3, step, overshoot, max_value_error, max_t_error);
}
//------------------------------------------------------------------------------
# endif // __IMGUI_BEZIER_MATH_INL__
+452
View File
@@ -0,0 +1,452 @@
# define IMGUI_DEFINE_MATH_OPERATORS
# include "imgui_canvas.h"
# include <type_traits>
// https://stackoverflow.com/a/36079786
# define DECLARE_HAS_MEMBER(__trait_name__, __member_name__) \
\
template <typename __boost_has_member_T__> \
class __trait_name__ \
{ \
using check_type = ::std::remove_const_t<__boost_has_member_T__>; \
struct no_type {char x[2];}; \
using yes_type = char; \
\
struct base { void __member_name__() {}}; \
struct mixin : public base, public check_type {}; \
\
template <void (base::*)()> struct aux {}; \
\
template <typename U> static no_type test(aux<&U::__member_name__>*); \
template <typename U> static yes_type test(...); \
\
public: \
\
static constexpr bool value = (sizeof(yes_type) == sizeof(test<mixin>(0))); \
}
namespace ImCanvasDetails {
DECLARE_HAS_MEMBER(HasFringeScale, _FringeScale);
struct FringeScaleRef
{
// Overload is present when ImDrawList does have _FringeScale member variable.
template <typename T>
static float& Get(typename std::enable_if<HasFringeScale<T>::value, T>::type* drawList)
{
return drawList->_FringeScale;
}
// Overload is present when ImDrawList does not have _FringeScale member variable.
template <typename T>
static float& Get(typename std::enable_if<!HasFringeScale<T>::value, T>::type*)
{
static float placeholder = 1.0f;
return placeholder;
}
};
} // namespace ImCanvasDetails
// Returns a reference to _FringeScale extension to ImDrawList
//
// If ImDrawList does not have _FringeScale a placeholder is returned.
static inline float& ImFringeScaleRef(ImDrawList* drawList)
{
using namespace ImCanvasDetails;
return FringeScaleRef::Get<ImDrawList>(drawList);
}
static inline ImVec2 ImSelectPositive(const ImVec2& lhs, const ImVec2& rhs) { return ImVec2(lhs.x > 0.0f ? lhs.x : rhs.x, lhs.y > 0.0f ? lhs.y : rhs.y); }
bool ImGuiEx::Canvas::Begin(const char* id, const ImVec2& size)
{
return Begin(ImGui::GetID(id), size);
}
bool ImGuiEx::Canvas::Begin(ImGuiID id, const ImVec2& size)
{
IM_ASSERT(m_InBeginEnd == false);
m_WidgetPosition = ImGui::GetCursorScreenPos();
m_WidgetSize = ImSelectPositive(size, ImGui::GetContentRegionAvail());
m_WidgetRect = ImRect(m_WidgetPosition, m_WidgetPosition + m_WidgetSize);
m_DrawList = ImGui::GetWindowDrawList();
UpdateViewTransformPosition();
if (ImGui::IsClippedEx(m_WidgetRect, id)) // IsClippedEx dropped its 3rd arg upstream in ImGui
return false;
// Save current channel, so we can assert when user
// call canvas API with different one.
m_ExpectedChannel = m_DrawList->_Splitter._Current;
// #debug: Canvas content.
//m_DrawList->AddRectFilled(m_StartPos, m_StartPos + m_CurrentSize, IM_COL32(0, 0, 0, 64));
m_DrawList->AddRect(m_WidgetRect.Min, m_WidgetRect.Max, IM_COL32(255, 0, 255, 64));
ImGui::SetCursorScreenPos(ImVec2(0.0f, 0.0f));
# if IMGUI_EX_CANVAS_DEFERED()
m_Ranges.resize(0);
# endif
SaveInputState();
EnterLocalSpace();
// Emit dummy widget matching bounds of the canvas.
ImGui::SetCursorScreenPos(m_ViewRect.Min);
ImGui::Dummy(m_ViewRect.GetSize());
ImGui::SetCursorScreenPos(ImVec2(0.0f, 0.0f));
m_InBeginEnd = true;
return true;
}
void ImGuiEx::Canvas::End()
{
// If you're here your call to Begin() returned false,
// or Begin() wasn't called at all.
IM_ASSERT(m_InBeginEnd == true);
// If you're here, please make sure you do not interleave
// channel splitter with canvas.
// Always call canvas function with using same channel.
IM_ASSERT(m_DrawList->_Splitter._Current == m_ExpectedChannel);
//auto& io = ImGui::GetIO();
// Check: Unmatched calls to Suspend() / Resume(). Please check your code.
IM_ASSERT(m_SuspendCounter == 0);
LeaveLocalSpace();
// Emit dummy widget matching bounds of the canvas.
ImGui::SetCursorScreenPos(m_WidgetPosition);
ImGui::Dummy(m_WidgetSize);
// #debug: Rect around canvas. Content should be inside these bounds.
//m_DrawList->AddRect(m_WidgetPosition - ImVec2(1.0f, 1.0f), m_WidgetPosition + m_WidgetSize + ImVec2(1.0f, 1.0f), IM_COL32(196, 0, 0, 255));
m_InBeginEnd = false;
}
void ImGuiEx::Canvas::SetView(const ImVec2& origin, float scale)
{
SetView(CanvasView(origin, scale));
}
void ImGuiEx::Canvas::SetView(const CanvasView& view)
{
if (m_InBeginEnd)
LeaveLocalSpace();
if (m_View.Origin.x != view.Origin.x || m_View.Origin.y != view.Origin.y)
{
m_View.Origin = view.Origin;
UpdateViewTransformPosition();
}
if (m_View.Scale != view.Scale)
{
m_View.Scale = view.Scale;
m_View.InvScale = view.InvScale;
}
if (m_InBeginEnd)
EnterLocalSpace();
}
void ImGuiEx::Canvas::CenterView(const ImVec2& canvasPoint)
{
auto view = CalcCenterView(canvasPoint);
SetView(view);
}
ImGuiEx::CanvasView ImGuiEx::Canvas::CalcCenterView(const ImVec2& canvasPoint) const
{
auto localCenter = ToLocal(m_WidgetPosition + m_WidgetSize * 0.5f);
auto localOffset = canvasPoint - localCenter;
auto offset = FromLocalV(localOffset);
return CanvasView{ m_View.Origin - offset, m_View.Scale };
}
void ImGuiEx::Canvas::CenterView(const ImRect& canvasRect)
{
auto view = CalcCenterView(canvasRect);
SetView(view);
}
ImGuiEx::CanvasView ImGuiEx::Canvas::CalcCenterView(const ImRect& canvasRect) const
{
auto canvasRectSize = canvasRect.GetSize();
if (canvasRectSize.x <= 0.0f || canvasRectSize.y <= 0.0f)
return View();
auto widgetAspectRatio = m_WidgetSize.y > 0.0f ? m_WidgetSize.x / m_WidgetSize.y : 0.0f;
auto canvasRectAspectRatio = canvasRectSize.y > 0.0f ? canvasRectSize.x / canvasRectSize.y : 0.0f;
if (widgetAspectRatio <= 0.0f || canvasRectAspectRatio <= 0.0f)
return View();
auto newOrigin = m_View.Origin;
auto newScale = m_View.Scale;
if (canvasRectAspectRatio > widgetAspectRatio)
{
// width span across view
newScale = m_WidgetSize.x / canvasRectSize.x;
newOrigin = canvasRect.Min * -newScale;
newOrigin.y += (m_WidgetSize.y - canvasRectSize.y * newScale) * 0.5f;
}
else
{
// height span across view
newScale = m_WidgetSize.y / canvasRectSize.y;
newOrigin = canvasRect.Min * -newScale;
newOrigin.x += (m_WidgetSize.x - canvasRectSize.x * newScale) * 0.5f;
}
return CanvasView{ newOrigin, newScale };
}
void ImGuiEx::Canvas::Suspend()
{
// If you're here, please make sure you do not interleave
// channel splitter with canvas.
// Always call canvas function with using same channel.
IM_ASSERT(m_DrawList->_Splitter._Current == m_ExpectedChannel);
if (m_SuspendCounter == 0)
LeaveLocalSpace();
++m_SuspendCounter;
}
void ImGuiEx::Canvas::Resume()
{
// If you're here, please make sure you do not interleave
// channel splitter with canvas.
// Always call canvas function with using same channel.
IM_ASSERT(m_DrawList->_Splitter._Current == m_ExpectedChannel);
// Check: Number of calls to Resume() do not match calls to Suspend(). Please check your code.
IM_ASSERT(m_SuspendCounter > 0);
if (--m_SuspendCounter == 0)
EnterLocalSpace();
}
ImVec2 ImGuiEx::Canvas::FromLocal(const ImVec2& point) const
{
return point * m_View.Scale + m_ViewTransformPosition;
}
ImVec2 ImGuiEx::Canvas::FromLocal(const ImVec2& point, const CanvasView& view) const
{
return point * view.Scale + view.Origin + m_WidgetPosition;
}
ImVec2 ImGuiEx::Canvas::FromLocalV(const ImVec2& vector) const
{
return vector * m_View.Scale;
}
ImVec2 ImGuiEx::Canvas::FromLocalV(const ImVec2& vector, const CanvasView& view) const
{
return vector * view.Scale;
}
ImVec2 ImGuiEx::Canvas::ToLocal(const ImVec2& point) const
{
return (point - m_ViewTransformPosition) * m_View.InvScale;
}
ImVec2 ImGuiEx::Canvas::ToLocal(const ImVec2& point, const CanvasView& view) const
{
return (point - view.Origin - m_WidgetPosition) * view.InvScale;
}
ImVec2 ImGuiEx::Canvas::ToLocalV(const ImVec2& vector) const
{
return vector * m_View.InvScale;
}
ImVec2 ImGuiEx::Canvas::ToLocalV(const ImVec2& vector, const CanvasView& view) const
{
return vector * view.InvScale;
}
ImRect ImGuiEx::Canvas::CalcViewRect(const CanvasView& view) const
{
ImRect result;
result.Min = ImVec2(-view.Origin.x, -view.Origin.y) * view.InvScale;
result.Max = (m_WidgetSize - view.Origin) * view.InvScale;
return result;
}
void ImGuiEx::Canvas::UpdateViewTransformPosition()
{
m_ViewTransformPosition = m_View.Origin + m_WidgetPosition;
}
void ImGuiEx::Canvas::SaveInputState()
{
auto& io = ImGui::GetIO();
m_MousePosBackup = io.MousePos;
m_MousePosPrevBackup = io.MousePosPrev;
for (auto i = 0; i < IM_ARRAYSIZE(m_MouseClickedPosBackup); ++i)
m_MouseClickedPosBackup[i] = io.MouseClickedPos[i];
// Record cursor max to prevent scrollbars from appearing.
m_WindowCursorMaxBackup = ImGui::GetCurrentWindow()->DC.CursorMaxPos;
}
void ImGuiEx::Canvas::RestoreInputState()
{
auto& io = ImGui::GetIO();
io.MousePos = m_MousePosBackup;
io.MousePosPrev = m_MousePosPrevBackup;
for (auto i = 0; i < IM_ARRAYSIZE(m_MouseClickedPosBackup); ++i)
io.MouseClickedPos[i] = m_MouseClickedPosBackup[i];
ImGui::GetCurrentWindow()->DC.CursorMaxPos = m_WindowCursorMaxBackup;
}
void ImGuiEx::Canvas::EnterLocalSpace()
{
// Prepare ImDrawList for drawing in local coordinate system:
// - determine visible part of the canvas
// - start unique draw command
// - add clip rect matching canvas size
// - record current command index
// - record current vertex write index
// Determine visible part of the canvas. Make it before
// adding new command, to avoid round rip where command
// is removed in PopClipRect() and added again next PushClipRect().
ImGui::PushClipRect(m_WidgetPosition, m_WidgetPosition + m_WidgetSize, true);
auto clipped_clip_rect = m_DrawList->_ClipRectStack.back();
ImGui::PopClipRect();
// Make sure we do not share draw command with anyone. We don't want to mess
// with someones clip rectangle.
// #FIXME:
// This condition is not enough to avoid when user choose
// to use channel splitter.
//
// To deal with Suspend()/Resume() calls empty draw command
// is always added then splitter is active. Otherwise
// channel merger will collapse our draw command one with
// different clip rectangle.
//
// More investigation is needed. To get to the bottom of this.
if ((!m_DrawList->CmdBuffer.empty() && m_DrawList->CmdBuffer.back().ElemCount > 0) || m_DrawList->_Splitter._Count > 1)
m_DrawList->AddDrawCmd();
# if IMGUI_EX_CANVAS_DEFERED()
m_Ranges.resize(m_Ranges.Size + 1);
m_CurrentRange = &m_Ranges.back();
m_CurrentRange->BeginComandIndex = ImMax(m_DrawList->CmdBuffer.Size - 1, 0);
m_CurrentRange->BeginVertexIndex = m_DrawList->_VtxCurrentIdx;
# endif
m_DrawListCommadBufferSize = ImMax(m_DrawList->CmdBuffer.Size - 1, 0);
m_DrawListStartVertexIndex = m_DrawList->_VtxCurrentIdx;
// Clip rectangle in parent canvas space and move it to local space.
clipped_clip_rect.x = (clipped_clip_rect.x - m_ViewTransformPosition.x) * m_View.InvScale;
clipped_clip_rect.y = (clipped_clip_rect.y - m_ViewTransformPosition.y) * m_View.InvScale;
clipped_clip_rect.z = (clipped_clip_rect.z - m_ViewTransformPosition.x) * m_View.InvScale;
clipped_clip_rect.w = (clipped_clip_rect.w - m_ViewTransformPosition.y) * m_View.InvScale;
ImGui::PushClipRect(ImVec2(clipped_clip_rect.x, clipped_clip_rect.y), ImVec2(clipped_clip_rect.z, clipped_clip_rect.w), false);
// Transform mouse position to local space.
auto& io = ImGui::GetIO();
io.MousePos = (m_MousePosBackup - m_ViewTransformPosition) * m_View.InvScale;
io.MousePosPrev = (m_MousePosPrevBackup - m_ViewTransformPosition) * m_View.InvScale;
for (auto i = 0; i < IM_ARRAYSIZE(m_MouseClickedPosBackup); ++i)
io.MouseClickedPos[i] = (m_MouseClickedPosBackup[i] - m_ViewTransformPosition) * m_View.InvScale;
m_ViewRect = CalcViewRect(m_View);;
auto& fringeScale = ImFringeScaleRef(m_DrawList);
m_LastFringeScale = fringeScale;
fringeScale *= m_View.InvScale;
}
void ImGuiEx::Canvas::LeaveLocalSpace()
{
IM_ASSERT(m_DrawList->_Splitter._Current == m_ExpectedChannel);
# if IMGUI_EX_CANVAS_DEFERED()
IM_ASSERT(m_CurrentRange != nullptr);
m_CurrentRange->EndVertexIndex = m_DrawList->_VtxCurrentIdx;
m_CurrentRange->EndCommandIndex = m_DrawList->CmdBuffer.size();
if (m_CurrentRange->BeginVertexIndex == m_CurrentRange->EndVertexIndex)
{
// Drop empty range
m_Ranges.resize(m_Ranges.Size - 1);
}
m_CurrentRange = nullptr;
# endif
// Move vertices to screen space.
auto vertex = m_DrawList->VtxBuffer.Data + m_DrawListStartVertexIndex;
auto vertexEnd = m_DrawList->VtxBuffer.Data + m_DrawList->_VtxCurrentIdx;
// If canvas view is not scaled take a faster path.
if (m_View.Scale != 1.0f)
{
while (vertex < vertexEnd)
{
vertex->pos.x = vertex->pos.x * m_View.Scale + m_ViewTransformPosition.x;
vertex->pos.y = vertex->pos.y * m_View.Scale + m_ViewTransformPosition.y;
++vertex;
}
// Move clip rectangles to screen space.
for (int i = m_DrawListCommadBufferSize; i < m_DrawList->CmdBuffer.size(); ++i)
{
auto& command = m_DrawList->CmdBuffer[i];
command.ClipRect.x = command.ClipRect.x * m_View.Scale + m_ViewTransformPosition.x;
command.ClipRect.y = command.ClipRect.y * m_View.Scale + m_ViewTransformPosition.y;
command.ClipRect.z = command.ClipRect.z * m_View.Scale + m_ViewTransformPosition.x;
command.ClipRect.w = command.ClipRect.w * m_View.Scale + m_ViewTransformPosition.y;
}
}
else
{
while (vertex < vertexEnd)
{
vertex->pos.x = vertex->pos.x + m_ViewTransformPosition.x;
vertex->pos.y = vertex->pos.y + m_ViewTransformPosition.y;
++vertex;
}
// Move clip rectangles to screen space.
for (int i = m_DrawListCommadBufferSize; i < m_DrawList->CmdBuffer.size(); ++i)
{
auto& command = m_DrawList->CmdBuffer[i];
command.ClipRect.x = command.ClipRect.x + m_ViewTransformPosition.x;
command.ClipRect.y = command.ClipRect.y + m_ViewTransformPosition.y;
command.ClipRect.z = command.ClipRect.z + m_ViewTransformPosition.x;
command.ClipRect.w = command.ClipRect.w + m_ViewTransformPosition.y;
}
}
auto& fringeScale = ImFringeScaleRef(m_DrawList);
fringeScale = m_LastFringeScale;
// And pop \o/
ImGui::PopClipRect();
RestoreInputState();
}
+250
View File
@@ -0,0 +1,250 @@
// Canvas widget - view over infinite virtual space.
//
// Canvas allows you to draw your widgets anywhere over infinite space and provide
// view over it with support for panning and scaling.
//
// When you enter a canvas ImGui is moved to virtual space which mean:
// - ImGui::GetCursorScreenPos() return (0, 0) and which correspond to top left corner
// of the canvas on the screen (this can be changed usign CanvasView()).
// - Mouse input is brought to canvas space, so widgets works as usual.
// - Everything you draw with ImDrawList will be in virtual space.
//
// By default origin point is on top left corner of canvas widget. It can be
// changed with call to CanvasView() where you can specify what part of space
// should be viewed by setting viewport origin point and scale. Current state
// can be queried with CanvasViewOrigin() and CanvasViewScale().
//
// Viewport size is controlled by 'size' parameter in BeginCanvas(). You can query
// it using CanvasContentMin/Max/Size functions. They are useful if you to not specify
// canvas size in which case all free space is used.
//
// Bounds of visible region of infinite space can be queried using CanvasViewMin/Max/Size
// functions. Everything that is drawn outside of this region will be clipped
// as usual in ImGui.
//
// While drawing inside canvas you can translate position from world (usual ImGui space)
// to virtual space and back usign CanvasFromWorld()/CanvasToWorld().
//
// Canvas can be nested in each other (they are regular widgets after all). There
// is a way to transform position between current and parent canvas with
// CanvasFromParent()/CanvasToParent().
//
// Sometimes in more elaborate scenarios you want to move out canvas virtual space,
// do something and came back. You can do that with SuspendCanvas() and ResumeCanvas().
//
// Note:
// It is not valid to call canvas API outside of BeginCanvas() / EndCanvas() scope.
//
// LICENSE
// This software is dual-licensed to the public domain and under the following
// license: you are granted a perpetual, irrevocable license to copy, modify,
// publish, and distribute this file as you see fit.
//
// CREDITS
// Written by Michal Cichon
# ifndef __IMGUI_EX_CANVAS_H__
# define __IMGUI_EX_CANVAS_H__
# pragma once
# include <imgui.h>
# include <imgui_internal.h> // ImRect, ImFloor
namespace ImGuiEx {
struct CanvasView
{
ImVec2 Origin;
float Scale = 1.0f;
float InvScale = 1.0f;
CanvasView() = default;
CanvasView(const ImVec2& origin, float scale)
: Origin(origin)
, Scale(scale)
, InvScale(scale ? 1.0f / scale : 0.0f)
{
}
void Set(const ImVec2& origin, float scale)
{
*this = CanvasView(origin, scale);
}
};
// Canvas widget represent view over infinite plane.
//
// It acts like a child window without scroll bars with
// ability to zoom to specific part of canvas plane.
//
// Widgets are clipped according to current view exactly
// same way ImGui do. To avoid `missing widgets` artifacts first
// setup visible region with SetView() then draw content.
//
// Everything drawn with ImDrawList betwen calls to Begin()/End()
// will be drawn on canvas plane. This behavior can be suspended
// by calling Suspend() and resumed by calling Resume().
//
// Warning:
// Please do not interleave canvas with use of channel splitter.
// Keep channel splitter contained inside canvas or always
// call canvas functions from same channel.
struct Canvas
{
// Begins drawing content of canvas plane.
//
// When false is returned that mean canvas is not visible to the
// user can drawing should be skipped and End() not called.
// When true is returned drawing must be ended with call to End().
//
// If any size component is equal to zero or less canvas will
// automatically expand to all available area on that axis.
// So (0, 300) will take horizontal space and have height
// of 300 points. (0, 0) will take all remaining space of
// the window.
//
// You can query size of the canvas while it is being drawn
// by calling Rect().
bool Begin(const char* id, const ImVec2& size);
bool Begin(ImGuiID id, const ImVec2& size);
// Ends interaction with canvas plane.
//
// Must be called only when Begin() retuned true.
void End();
// Sets visible region of canvas plane.
//
// Origin is an offset of infinite plane origin from top left
// corner of the canvas.
//
// Scale greater than 1 make canvas content be bigger, less than 1 smaller.
void SetView(const ImVec2& origin, float scale);
void SetView(const CanvasView& view);
// Centers view over specific point on canvas plane.
//
// View will be centered on specific point by changing origin
// but not scale.
void CenterView(const ImVec2& canvasPoint);
// Calculates view over specific point on canvas plane.
CanvasView CalcCenterView(const ImVec2& canvasPoint) const;
// Centers view over specific rectangle on canvas plane.
//
// Whole rectangle will fit in canvas view. This will affect both
// origin and scale.
void CenterView(const ImRect& canvasRect);
// Calculates view over specific rectangle on canvas plane.
CanvasView CalcCenterView(const ImRect& canvasRect) const;
// Suspends canvas by returning to normal ImGui transformation space.
// While suspended UI will not be drawn on canvas plane.
//
// Calls to Suspend()/Resume() are symetrical. Each call to Suspend()
// must be matched with call to Resume().
void Suspend();
void Resume();
// Transforms point from canvas plane to ImGui.
ImVec2 FromLocal(const ImVec2& point) const;
ImVec2 FromLocal(const ImVec2& point, const CanvasView& view) const;
// Transforms vector from canvas plant to ImGui.
ImVec2 FromLocalV(const ImVec2& vector) const;
ImVec2 FromLocalV(const ImVec2& vector, const CanvasView& view) const;
// Transforms point from ImGui to canvas plane.
ImVec2 ToLocal(const ImVec2& point) const;
ImVec2 ToLocal(const ImVec2& point, const CanvasView& view) const;
// Transforms vector from ImGui to canvas plane.
ImVec2 ToLocalV(const ImVec2& vector) const;
ImVec2 ToLocalV(const ImVec2& vector, const CanvasView& view) const;
// Returns widget bounds.
//
// Note:
// Rect is valid after call to Begin().
const ImRect& Rect() const { return m_WidgetRect; }
// Returns visible region on canvas plane (in canvas plane coordinates).
const ImRect& ViewRect() const { return m_ViewRect; }
// Calculates visible region for view.
ImRect CalcViewRect(const CanvasView& view) const;
// Returns current view.
const CanvasView& View() const { return m_View; }
// Returns origin of the view.
//
// Origin is an offset of infinite plane origin from top left
// corner of the canvas.
const ImVec2& ViewOrigin() const { return m_View.Origin; }
// Returns scale of the view.
float ViewScale() const { return m_View.Scale; }
// Returns true if canvas is suspended.
//
// See: Suspend()/Resume()
bool IsSuspended() const { return m_SuspendCounter > 0; }
private:
# define IMGUI_EX_CANVAS_DEFERED() 0
# if IMGUI_EX_CANVAS_DEFERED()
struct Range
{
int BeginVertexIndex = 0;
int EndVertexIndex = 0;
int BeginComandIndex = 0;
int EndCommandIndex = 0;
};
# endif
void UpdateViewTransformPosition();
void SaveInputState();
void RestoreInputState();
void EnterLocalSpace();
void LeaveLocalSpace();
bool m_InBeginEnd = false;
ImVec2 m_WidgetPosition;
ImVec2 m_WidgetSize;
ImRect m_WidgetRect;
ImDrawList* m_DrawList = nullptr;
int m_ExpectedChannel = 0;
# if IMGUI_EX_CANVAS_DEFERED()
ImVector<Range> m_Ranges;
Range* m_CurrentRange = nullptr;
# endif
int m_DrawListCommadBufferSize = 0;
int m_DrawListStartVertexIndex = 0;
CanvasView m_View;
ImRect m_ViewRect;
ImVec2 m_ViewTransformPosition;
int m_SuspendCounter = 0;
float m_LastFringeScale = 1.0f;
ImVec2 m_MousePosBackup;
ImVec2 m_MousePosPrevBackup;
ImVec2 m_MouseClickedPosBackup[IM_ARRAYSIZE(ImGuiIO::MouseClickedPos)];
ImVec2 m_WindowCursorMaxBackup;
};
} // namespace ImGuiEx
# endif // __IMGUI_EX_CANVAS_H__
@@ -0,0 +1,76 @@
//------------------------------------------------------------------------------
// LICENSE
// This software is dual-licensed to the public domain and under the following
// license: you are granted a perpetual, irrevocable license to copy, modify,
// publish, and distribute this file as you see fit.
//
// CREDITS
// Written by Michal Cichon
//------------------------------------------------------------------------------
# ifndef __IMGUI_EXTRA_MATH_H__
# define __IMGUI_EXTRA_MATH_H__
# pragma once
//------------------------------------------------------------------------------
# include <imgui.h>
# ifndef IMGUI_DEFINE_MATH_OPERATORS
# define IMGUI_DEFINE_MATH_OPERATORS
# endif
# include <imgui_internal.h>
//------------------------------------------------------------------------------
struct ImLine
{
ImVec2 A, B;
};
//------------------------------------------------------------------------------
// Modern ImGui (1.89.4+) already defines these four operators in imgui.h
// itself when IMGUI_DEFINE_MATH_OPERATORS is set before its first include —
// guard against redefinition against this project's ImGui version.
# ifndef IMGUI_DEFINE_MATH_OPERATORS_IMPLEMENTED
inline bool operator==(const ImVec2& lhs, const ImVec2& rhs);
inline bool operator!=(const ImVec2& lhs, const ImVec2& rhs);
inline ImVec2 operator*(const float lhs, const ImVec2& rhs);
inline ImVec2 operator-(const ImVec2& lhs);
# endif
//------------------------------------------------------------------------------
inline float ImLength(float v);
inline float ImLength(const ImVec2& v);
inline float ImLengthSqr(float v);
inline ImVec2 ImNormalized(const ImVec2& v);
//------------------------------------------------------------------------------
inline bool ImRect_IsEmpty(const ImRect& rect);
inline ImVec2 ImRect_ClosestPoint(const ImRect& rect, const ImVec2& p, bool snap_to_edge);
inline ImVec2 ImRect_ClosestPoint(const ImRect& rect, const ImVec2& p, bool snap_to_edge, float radius);
inline ImVec2 ImRect_ClosestPoint(const ImRect& rect, const ImRect& b);
inline ImLine ImRect_ClosestLine(const ImRect& rect_a, const ImRect& rect_b);
inline ImLine ImRect_ClosestLine(const ImRect& rect_a, const ImRect& rect_b, float radius_a, float radius_b);
//------------------------------------------------------------------------------
namespace ImEasing {
template <typename V, typename T>
inline V EaseOutQuad(V b, V c, T t)
{
return b - c * (t * (t - 2));
}
} // namespace ImEasing
//------------------------------------------------------------------------------
# include "imgui_extra_math.inl"
//------------------------------------------------------------------------------
# endif // __IMGUI_EXTRA_MATH_H__
@@ -0,0 +1,191 @@
//------------------------------------------------------------------------------
// LICENSE
// This software is dual-licensed to the public domain and under the following
// license: you are granted a perpetual, irrevocable license to copy, modify,
// publish, and distribute this file as you see fit.
//
// CREDITS
// Written by Michal Cichon
//------------------------------------------------------------------------------
# ifndef __IMGUI_EXTRA_MATH_INL__
# define __IMGUI_EXTRA_MATH_INL__
# pragma once
//------------------------------------------------------------------------------
# include "imgui_extra_math.h"
//------------------------------------------------------------------------------
// See guard note in imgui_extra_math.h — already provided by imgui.h on
// this project's ImGui version.
# ifndef IMGUI_DEFINE_MATH_OPERATORS_IMPLEMENTED
inline bool operator==(const ImVec2& lhs, const ImVec2& rhs)
{
return lhs.x == rhs.x && lhs.y == rhs.y;
}
inline bool operator!=(const ImVec2& lhs, const ImVec2& rhs)
{
return lhs.x != rhs.x || lhs.y != rhs.y;
}
inline ImVec2 operator*(const float lhs, const ImVec2& rhs)
{
return ImVec2(lhs * rhs.x, lhs * rhs.y);
}
inline ImVec2 operator-(const ImVec2& lhs)
{
return ImVec2(-lhs.x, -lhs.y);
}
# endif
//------------------------------------------------------------------------------
inline float ImLength(float v)
{
return v;
}
inline float ImLength(const ImVec2& v)
{
return ImSqrt(ImLengthSqr(v));
}
inline float ImLengthSqr(float v)
{
return v * v;
}
inline ImVec2 ImNormalized(const ImVec2& v)
{
return v * ImInvLength(v, 0.0f);
}
//------------------------------------------------------------------------------
inline bool ImRect_IsEmpty(const ImRect& rect)
{
return rect.Min.x >= rect.Max.x
|| rect.Min.y >= rect.Max.y;
}
inline ImVec2 ImRect_ClosestPoint(const ImRect& rect, const ImVec2& p, bool snap_to_edge)
{
if (!snap_to_edge && rect.Contains(p))
return p;
return ImVec2(
(p.x > rect.Max.x) ? rect.Max.x : (p.x < rect.Min.x ? rect.Min.x : p.x),
(p.y > rect.Max.y) ? rect.Max.y : (p.y < rect.Min.y ? rect.Min.y : p.y)
);
}
inline ImVec2 ImRect_ClosestPoint(const ImRect& rect, const ImVec2& p, bool snap_to_edge, float radius)
{
auto point = ImRect_ClosestPoint(rect, p, snap_to_edge);
const auto offset = p - point;
const auto distance_sq = offset.x * offset.x + offset.y * offset.y;
if (distance_sq <= 0)
return point;
const auto distance = ImSqrt(distance_sq);
return point + offset * (ImMin(distance, radius) * (1.0f / distance));
}
inline ImVec2 ImRect_ClosestPoint(const ImRect& rect, const ImRect& other)
{
ImVec2 result;
if (other.Min.x >= rect.Max.x)
result.x = rect.Max.x;
else if (other.Max.x <= rect.Min.x)
result.x = rect.Min.x;
else
result.x = (ImMax(rect.Min.x, other.Min.x) + ImMin(rect.Max.x, other.Max.x)) / 2;
if (other.Min.y >= rect.Max.y)
result.y = rect.Max.y;
else if (other.Max.y <= rect.Min.y)
result.y = rect.Min.y;
else
result.y = (ImMax(rect.Min.y, other.Min.y) + ImMin(rect.Max.y, other.Max.y)) / 2;
return result;
}
inline ImLine ImRect_ClosestLine(const ImRect& rect_a, const ImRect& rect_b)
{
ImLine result;
result.A = ImRect_ClosestPoint(rect_a, rect_b);
result.B = ImRect_ClosestPoint(rect_b, rect_a);
auto distribute = [](float& a, float& b, float a0, float a1, float b0, float b1)
{
if (a0 >= b1 || a1 <= b0)
return;
const auto aw = a1 - a0;
const auto bw = b1 - b0;
if (aw > bw)
{
b = b0 + bw - bw * (a - a0) / aw;
a = b;
}
else if (aw < bw)
{
a = a0 + aw - aw * (b - b0) / bw;
b = a;
}
};
distribute(result.A.x, result.B.x, rect_a.Min.x, rect_a.Max.x, rect_b.Min.x, rect_b.Max.x);
distribute(result.A.y, result.B.y, rect_a.Min.y, rect_a.Max.y, rect_b.Min.y, rect_b.Max.y);
return result;
}
inline ImLine ImRect_ClosestLine(const ImRect& rect_a, const ImRect& rect_b, float radius_a, float radius_b)
{
auto line = ImRect_ClosestLine(rect_a, rect_b);
if (radius_a < 0)
radius_a = 0;
if (radius_b < 0)
radius_b = 0;
if (radius_a == 0 && radius_b == 0)
return line;
const auto offset = line.B - line.A;
const auto length_sq = offset.x * offset.x + offset.y * offset.y;
const auto radius_a_sq = radius_a * radius_a;
const auto radius_b_sq = radius_b * radius_b;
if (length_sq <= 0)
return line;
const auto length = ImSqrt(length_sq);
const auto direction = ImVec2(offset.x / length, offset.y / length);
const auto total_radius_sq = radius_a_sq + radius_b_sq;
if (total_radius_sq > length_sq)
{
const auto scale = length / (radius_a + radius_b);
radius_a *= scale;
radius_b *= scale;
}
line.A = line.A + (direction * radius_a);
line.B = line.B - (direction * radius_b);
return line;
}
//------------------------------------------------------------------------------
# endif // __IMGUI_EXTRA_MATH_INL__
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,637 @@
//------------------------------------------------------------------------------
// LICENSE
// This software is dual-licensed to the public domain and under the following
// license: you are granted a perpetual, irrevocable license to copy, modify,
// publish, and distribute this file as you see fit.
//
// CREDITS
// Written by Michal Cichon
//------------------------------------------------------------------------------
# include "imgui_node_editor_internal.h"
# include <algorithm>
//------------------------------------------------------------------------------
static ax::NodeEditor::Detail::EditorContext* s_Editor = nullptr;
//------------------------------------------------------------------------------
template <typename C, typename I, typename F>
static int BuildIdList(C& container, I* list, int listSize, F&& accept)
{
if (list != nullptr)
{
int count = 0;
for (auto object : container)
{
if (listSize <= 0)
break;
if (accept(object))
{
list[count] = I(object->ID().AsPointer());
++count;
--listSize;
}
}
return count;
}
else
return static_cast<int>(std::count_if(container.begin(), container.end(), accept));
}
//------------------------------------------------------------------------------
ax::NodeEditor::EditorContext* ax::NodeEditor::CreateEditor(const Config* config)
{
return reinterpret_cast<ax::NodeEditor::EditorContext*>(new ax::NodeEditor::Detail::EditorContext(config));
}
void ax::NodeEditor::DestroyEditor(EditorContext* ctx)
{
if (GetCurrentEditor() == ctx)
SetCurrentEditor(nullptr);
auto editor = reinterpret_cast<ax::NodeEditor::Detail::EditorContext*>(ctx);
delete editor;
}
void ax::NodeEditor::SetCurrentEditor(EditorContext* ctx)
{
s_Editor = reinterpret_cast<ax::NodeEditor::Detail::EditorContext*>(ctx);
}
ax::NodeEditor::EditorContext* ax::NodeEditor::GetCurrentEditor()
{
return reinterpret_cast<ax::NodeEditor::EditorContext*>(s_Editor);
}
ax::NodeEditor::Style& ax::NodeEditor::GetStyle()
{
return s_Editor->GetStyle();
}
const char* ax::NodeEditor::GetStyleColorName(StyleColor colorIndex)
{
return s_Editor->GetStyle().GetColorName(colorIndex);
}
void ax::NodeEditor::PushStyleColor(StyleColor colorIndex, const ImVec4& color)
{
s_Editor->GetStyle().PushColor(colorIndex, color);
}
void ax::NodeEditor::PopStyleColor(int count)
{
s_Editor->GetStyle().PopColor(count);
}
void ax::NodeEditor::PushStyleVar(StyleVar varIndex, float value)
{
s_Editor->GetStyle().PushVar(varIndex, value);
}
void ax::NodeEditor::PushStyleVar(StyleVar varIndex, const ImVec2& value)
{
s_Editor->GetStyle().PushVar(varIndex, value);
}
void ax::NodeEditor::PushStyleVar(StyleVar varIndex, const ImVec4& value)
{
s_Editor->GetStyle().PushVar(varIndex, value);
}
void ax::NodeEditor::PopStyleVar(int count)
{
s_Editor->GetStyle().PopVar(count);
}
void ax::NodeEditor::Begin(const char* id, const ImVec2& size)
{
s_Editor->Begin(id, size);
}
void ax::NodeEditor::End()
{
s_Editor->End();
}
void ax::NodeEditor::BeginNode(NodeId id)
{
s_Editor->GetNodeBuilder().Begin(id);
}
void ax::NodeEditor::BeginPin(PinId id, PinKind kind)
{
s_Editor->GetNodeBuilder().BeginPin(id, kind);
}
void ax::NodeEditor::PinRect(const ImVec2& a, const ImVec2& b)
{
s_Editor->GetNodeBuilder().PinRect(a, b);
}
void ax::NodeEditor::PinPivotRect(const ImVec2& a, const ImVec2& b)
{
s_Editor->GetNodeBuilder().PinPivotRect(a, b);
}
void ax::NodeEditor::PinPivotSize(const ImVec2& size)
{
s_Editor->GetNodeBuilder().PinPivotSize(size);
}
void ax::NodeEditor::PinPivotScale(const ImVec2& scale)
{
s_Editor->GetNodeBuilder().PinPivotScale(scale);
}
void ax::NodeEditor::PinPivotAlignment(const ImVec2& alignment)
{
s_Editor->GetNodeBuilder().PinPivotAlignment(alignment);
}
void ax::NodeEditor::EndPin()
{
s_Editor->GetNodeBuilder().EndPin();
}
void ax::NodeEditor::Group(const ImVec2& size)
{
s_Editor->GetNodeBuilder().Group(size);
}
void ax::NodeEditor::EndNode()
{
s_Editor->GetNodeBuilder().End();
}
bool ax::NodeEditor::BeginGroupHint(NodeId nodeId)
{
return s_Editor->GetHintBuilder().Begin(nodeId);
}
ImVec2 ax::NodeEditor::GetGroupMin()
{
return s_Editor->GetHintBuilder().GetGroupMin();
}
ImVec2 ax::NodeEditor::GetGroupMax()
{
return s_Editor->GetHintBuilder().GetGroupMax();
}
ImDrawList* ax::NodeEditor::GetHintForegroundDrawList()
{
return s_Editor->GetHintBuilder().GetForegroundDrawList();
}
ImDrawList* ax::NodeEditor::GetHintBackgroundDrawList()
{
return s_Editor->GetHintBuilder().GetBackgroundDrawList();
}
void ax::NodeEditor::EndGroupHint()
{
s_Editor->GetHintBuilder().End();
}
ImDrawList* ax::NodeEditor::GetNodeBackgroundDrawList(NodeId nodeId)
{
if (auto node = s_Editor->FindNode(nodeId))
return s_Editor->GetNodeBuilder().GetUserBackgroundDrawList(node);
else
return nullptr;
}
bool ax::NodeEditor::Link(LinkId id, PinId startPinId, PinId endPinId, const ImVec4& color/* = ImVec4(1, 1, 1, 1)*/, float thickness/* = 1.0f*/)
{
return s_Editor->DoLink(id, startPinId, endPinId, ImColor(color), thickness);
}
void ax::NodeEditor::Flow(LinkId linkId)
{
if (auto link = s_Editor->FindLink(linkId))
s_Editor->Flow(link);
}
bool ax::NodeEditor::BeginCreate(const ImVec4& color, float thickness)
{
auto& context = s_Editor->GetItemCreator();
if (context.Begin())
{
context.SetStyle(ImColor(color), thickness);
return true;
}
else
return false;
}
bool ax::NodeEditor::QueryNewLink(PinId* startId, PinId* endId)
{
using Result = ax::NodeEditor::Detail::CreateItemAction::Result;
auto& context = s_Editor->GetItemCreator();
return context.QueryLink(startId, endId) == Result::True;
}
bool ax::NodeEditor::QueryNewLink(PinId* startId, PinId* endId, const ImVec4& color, float thickness)
{
using Result = ax::NodeEditor::Detail::CreateItemAction::Result;
auto& context = s_Editor->GetItemCreator();
auto result = context.QueryLink(startId, endId);
if (result != Result::Indeterminate)
context.SetStyle(ImColor(color), thickness);
return result == Result::True;
}
bool ax::NodeEditor::QueryNewNode(PinId* pinId)
{
using Result = ax::NodeEditor::Detail::CreateItemAction::Result;
auto& context = s_Editor->GetItemCreator();
return context.QueryNode(pinId) == Result::True;
}
bool ax::NodeEditor::QueryNewNode(PinId* pinId, const ImVec4& color, float thickness)
{
using Result = ax::NodeEditor::Detail::CreateItemAction::Result;
auto& context = s_Editor->GetItemCreator();
auto result = context.QueryNode(pinId);
if (result != Result::Indeterminate)
context.SetStyle(ImColor(color), thickness);
return result == Result::True;
}
bool ax::NodeEditor::AcceptNewItem()
{
using Result = ax::NodeEditor::Detail::CreateItemAction::Result;
auto& context = s_Editor->GetItemCreator();
return context.AcceptItem() == Result::True;
}
bool ax::NodeEditor::AcceptNewItem(const ImVec4& color, float thickness)
{
using Result = ax::NodeEditor::Detail::CreateItemAction::Result;
auto& context = s_Editor->GetItemCreator();
auto result = context.AcceptItem();
if (result != Result::Indeterminate)
context.SetStyle(ImColor(color), thickness);
return result == Result::True;
}
void ax::NodeEditor::RejectNewItem()
{
auto& context = s_Editor->GetItemCreator();
context.RejectItem();
}
void ax::NodeEditor::RejectNewItem(const ImVec4& color, float thickness)
{
using Result = ax::NodeEditor::Detail::CreateItemAction::Result;
auto& context = s_Editor->GetItemCreator();
if (context.RejectItem() != Result::Indeterminate)
context.SetStyle(ImColor(color), thickness);
}
void ax::NodeEditor::EndCreate()
{
auto& context = s_Editor->GetItemCreator();
context.End();
}
bool ax::NodeEditor::BeginDelete()
{
auto& context = s_Editor->GetItemDeleter();
return context.Begin();
}
bool ax::NodeEditor::QueryDeletedLink(LinkId* linkId, PinId* startId, PinId* endId)
{
auto& context = s_Editor->GetItemDeleter();
return context.QueryLink(linkId, startId, endId);
}
bool ax::NodeEditor::QueryDeletedNode(NodeId* nodeId)
{
auto& context = s_Editor->GetItemDeleter();
return context.QueryNode(nodeId);
}
bool ax::NodeEditor::AcceptDeletedItem()
{
auto& context = s_Editor->GetItemDeleter();
return context.AcceptItem();
}
void ax::NodeEditor::RejectDeletedItem()
{
auto& context = s_Editor->GetItemDeleter();
context.RejectItem();
}
void ax::NodeEditor::EndDelete()
{
auto& context = s_Editor->GetItemDeleter();
context.End();
}
void ax::NodeEditor::SetNodePosition(NodeId nodeId, const ImVec2& position)
{
s_Editor->SetNodePosition(nodeId, position);
}
ImVec2 ax::NodeEditor::GetNodePosition(NodeId nodeId)
{
return s_Editor->GetNodePosition(nodeId);
}
ImVec2 ax::NodeEditor::GetNodeSize(NodeId nodeId)
{
return s_Editor->GetNodeSize(nodeId);
}
void ax::NodeEditor::CenterNodeOnScreen(NodeId nodeId)
{
if (auto node = s_Editor->FindNode(nodeId))
node->CenterOnScreenInNextFrame();
}
void ax::NodeEditor::RestoreNodeState(NodeId nodeId)
{
if (auto node = s_Editor->FindNode(nodeId))
s_Editor->MarkNodeToRestoreState(node);
}
void ax::NodeEditor::Suspend()
{
s_Editor->Suspend();
}
void ax::NodeEditor::Resume()
{
s_Editor->Resume();
}
bool ax::NodeEditor::IsSuspended()
{
return s_Editor->IsSuspended();
}
bool ax::NodeEditor::IsActive()
{
return s_Editor->IsActive();
}
bool ax::NodeEditor::HasSelectionChanged()
{
return s_Editor->HasSelectionChanged();
}
int ax::NodeEditor::GetSelectedObjectCount()
{
return (int)s_Editor->GetSelectedObjects().size();
}
int ax::NodeEditor::GetSelectedNodes(NodeId* nodes, int size)
{
return BuildIdList(s_Editor->GetSelectedObjects(), nodes, size, [](auto object)
{
return object->AsNode() != nullptr;
});
}
int ax::NodeEditor::GetSelectedLinks(LinkId* links, int size)
{
return BuildIdList(s_Editor->GetSelectedObjects(), links, size, [](auto object)
{
return object->AsLink() != nullptr;
});
}
void ax::NodeEditor::ClearSelection()
{
s_Editor->ClearSelection();
}
void ax::NodeEditor::SelectNode(NodeId nodeId, bool append)
{
if (auto node = s_Editor->FindNode(nodeId))
{
if (append)
s_Editor->SelectObject(node);
else
s_Editor->SetSelectedObject(node);
}
}
void ax::NodeEditor::SelectLink(LinkId linkId, bool append)
{
if (auto link = s_Editor->FindLink(linkId))
{
if (append)
s_Editor->SelectObject(link);
else
s_Editor->SetSelectedObject(link);
}
}
void ax::NodeEditor::DeselectNode(NodeId nodeId)
{
if (auto node = s_Editor->FindNode(nodeId))
s_Editor->DeselectObject(node);
}
void ax::NodeEditor::DeselectLink(LinkId linkId)
{
if (auto link = s_Editor->FindLink(linkId))
s_Editor->DeselectObject(link);
}
bool ax::NodeEditor::DeleteNode(NodeId nodeId)
{
if (auto node = s_Editor->FindNode(nodeId))
return s_Editor->GetItemDeleter().Add(node);
else
return false;
}
bool ax::NodeEditor::DeleteLink(LinkId linkId)
{
if (auto link = s_Editor->FindLink(linkId))
return s_Editor->GetItemDeleter().Add(link);
else
return false;
}
void ax::NodeEditor::NavigateToContent(float duration)
{
s_Editor->NavigateTo(s_Editor->GetContentBounds(), true, duration);
}
void ax::NodeEditor::NavigateToSelection(bool zoomIn, float duration)
{
s_Editor->NavigateTo(s_Editor->GetSelectionBounds(), zoomIn, duration);
}
bool ax::NodeEditor::ShowNodeContextMenu(NodeId* nodeId)
{
return s_Editor->GetContextMenu().ShowNodeContextMenu(nodeId);
}
bool ax::NodeEditor::ShowPinContextMenu(PinId* pinId)
{
return s_Editor->GetContextMenu().ShowPinContextMenu(pinId);
}
bool ax::NodeEditor::ShowLinkContextMenu(LinkId* linkId)
{
return s_Editor->GetContextMenu().ShowLinkContextMenu(linkId);
}
bool ax::NodeEditor::ShowBackgroundContextMenu()
{
return s_Editor->GetContextMenu().ShowBackgroundContextMenu();
}
void ax::NodeEditor::EnableShortcuts(bool enable)
{
s_Editor->EnableShortcuts(enable);
}
bool ax::NodeEditor::AreShortcutsEnabled()
{
return s_Editor->AreShortcutsEnabled();
}
bool ax::NodeEditor::BeginShortcut()
{
return s_Editor->GetShortcut().Begin();
}
bool ax::NodeEditor::AcceptCut()
{
return s_Editor->GetShortcut().AcceptCut();
}
bool ax::NodeEditor::AcceptCopy()
{
return s_Editor->GetShortcut().AcceptCopy();
}
bool ax::NodeEditor::AcceptPaste()
{
return s_Editor->GetShortcut().AcceptPaste();
}
bool ax::NodeEditor::AcceptDuplicate()
{
return s_Editor->GetShortcut().AcceptDuplicate();
}
bool ax::NodeEditor::AcceptCreateNode()
{
return s_Editor->GetShortcut().AcceptCreateNode();
}
int ax::NodeEditor::GetActionContextSize()
{
return static_cast<int>(s_Editor->GetShortcut().m_Context.size());
}
int ax::NodeEditor::GetActionContextNodes(NodeId* nodes, int size)
{
return BuildIdList(s_Editor->GetSelectedObjects(), nodes, size, [](auto object)
{
return object->AsNode() != nullptr;
});
}
int ax::NodeEditor::GetActionContextLinks(LinkId* links, int size)
{
return BuildIdList(s_Editor->GetSelectedObjects(), links, size, [](auto object)
{
return object->AsLink() != nullptr;
});
}
void ax::NodeEditor::EndShortcut()
{
return s_Editor->GetShortcut().End();
}
float ax::NodeEditor::GetCurrentZoom()
{
return s_Editor->GetView().InvScale;
}
ax::NodeEditor::NodeId ax::NodeEditor::GetDoubleClickedNode()
{
return s_Editor->GetDoubleClickedNode();
}
ax::NodeEditor::PinId ax::NodeEditor::GetDoubleClickedPin()
{
return s_Editor->GetDoubleClickedPin();
}
ax::NodeEditor::LinkId ax::NodeEditor::GetDoubleClickedLink()
{
return s_Editor->GetDoubleClickedLink();
}
bool ax::NodeEditor::IsBackgroundClicked()
{
return s_Editor->IsBackgroundClicked();
}
bool ax::NodeEditor::IsBackgroundDoubleClicked()
{
return s_Editor->IsBackgroundDoubleClicked();
}
bool ax::NodeEditor::PinHadAnyLinks(PinId pinId)
{
return s_Editor->PinHadAnyLinks(pinId);
}
ImVec2 ax::NodeEditor::GetScreenSize()
{
return s_Editor->GetRect().GetSize();
}
ImVec2 ax::NodeEditor::ScreenToCanvas(const ImVec2& pos)
{
return s_Editor->ToCanvas(pos);
}
ImVec2 ax::NodeEditor::CanvasToScreen(const ImVec2& pos)
{
return s_Editor->ToScreen(pos);
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,55 @@
//------------------------------------------------------------------------------
// LICENSE
// This software is dual-licensed to the public domain and under the following
// license: you are granted a perpetual, irrevocable license to copy, modify,
// publish, and distribute this file as you see fit.
//
// CREDITS
// Written by Michal Cichon
//------------------------------------------------------------------------------
# ifndef __IMGUI_NODE_EDITOR_INTERNAL_INL__
# define __IMGUI_NODE_EDITOR_INTERNAL_INL__
# pragma once
//------------------------------------------------------------------------------
# include "imgui_node_editor_internal.h"
//------------------------------------------------------------------------------
namespace ax {
namespace NodeEditor {
namespace Detail {
//------------------------------------------------------------------------------
//inline ImRect ToRect(const ax::rectf& rect)
//{
// return ImRect(
// to_imvec(rect.top_left()),
// to_imvec(rect.bottom_right())
// );
//}
//
//inline ImRect ToRect(const ax::rect& rect)
//{
// return ImRect(
// to_imvec(rect.top_left()),
// to_imvec(rect.bottom_right())
// );
//}
inline ImRect ImGui_GetItemRect()
{
return ImRect(ImGui::GetItemRectMin(), ImGui::GetItemRectMax());
}
//------------------------------------------------------------------------------
} // namespace Detail
} // namespace Editor
} // namespace ax
//------------------------------------------------------------------------------
# endif // __IMGUI_NODE_EDITOR_INTERNAL_INL__