feat(shaders_lab): big colored pin circles, side layout, right-click delete
- Three-column node layout: input pins on the left edge, controls in the middle, output pin on the right edge. - Pins rendered as 14px filled circles with darker outline. Color is the node's kind (gen=blue, op=violet, blend=amber, output=red). - ed::PinPivotAlignment(0,0.5)/(1,0.5) so the cable starts/ends at the circle center. - Empty columns (gen with no inputs, output with no output) get a pin-sized Dummy so column widths stay consistent. - ed::Link now passes color (= source node kind) and 2.5px thickness. - Right-click on a link deletes it immediately via ed::ShowLinkContextMenu (no popup). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Binary file not shown.
@@ -70,6 +70,22 @@ static ImVec4 kind_color(DagKind kind) {
|
||||
return ImVec4(1, 1, 1, 1);
|
||||
}
|
||||
|
||||
static constexpr float PIN_RADIUS = 7.0f;
|
||||
static constexpr float PIN_DIAMETER = PIN_RADIUS * 2.0f;
|
||||
|
||||
// Draws a filled circle of `color` and reserves a 14x14 dummy item so the
|
||||
// node editor can compute the pin's hit rect from the surrounding BeginPin.
|
||||
// Adds an outline in a slightly darker tone for visibility.
|
||||
static void draw_pin_circle(ImVec4 color) {
|
||||
ImVec2 cursor = ImGui::GetCursorScreenPos();
|
||||
ImVec2 center = ImVec2(cursor.x + PIN_RADIUS, cursor.y + PIN_RADIUS);
|
||||
ImDrawList* dl = ImGui::GetWindowDrawList();
|
||||
dl->AddCircleFilled(center, PIN_RADIUS, ImGui::ColorConvertFloat4ToU32(color));
|
||||
ImVec4 border(color.x * 0.4f, color.y * 0.4f, color.z * 0.4f, 1.0f);
|
||||
dl->AddCircle(center, PIN_RADIUS, ImGui::ColorConvertFloat4ToU32(border), 0, 1.5f);
|
||||
ImGui::Dummy(ImVec2(PIN_DIAMETER, PIN_DIAMETER));
|
||||
}
|
||||
|
||||
// Topological sort using Kahn's algorithm.
|
||||
// Returns false if a cycle is detected (should not happen — BeginCreate rejects cycles).
|
||||
static bool topo_sort(std::vector<DagStep>& pipeline) {
|
||||
@@ -225,26 +241,30 @@ bool dag_node_editor(std::vector<DagStep>& pipeline) {
|
||||
ImGui::PopStyleColor();
|
||||
ImGui::Dummy(ImVec2(0, 2));
|
||||
|
||||
// Input pins (left column) + controls (middle) + output pin (right)
|
||||
// We use a simple layout: input pins vertically, then controls, then output pin
|
||||
int ni = def->num_inputs;
|
||||
bool has_output_pin = (def->kind != DagKind::Output);
|
||||
|
||||
// Input pins
|
||||
// ── Three-column layout: inputs · controls · output ──────────
|
||||
ImGui::BeginGroup(); // inputs column
|
||||
if (ni == 0) {
|
||||
ImGui::Dummy(ImVec2(PIN_DIAMETER, PIN_DIAMETER));
|
||||
}
|
||||
for (int k = 0; k < ni; ++k) {
|
||||
ed::BeginPin(ed::PinId(input_pin_id(step.editor_uid, k)), ed::PinKind::Input);
|
||||
char lbl[16];
|
||||
std::snprintf(lbl, sizeof(lbl), "-> in%d", k);
|
||||
ImGui::TextUnformatted(lbl);
|
||||
ed::PinPivotAlignment(ImVec2(0.0f, 0.5f));
|
||||
ed::PinPivotSize(ImVec2(0, 0));
|
||||
draw_pin_circle(col);
|
||||
ed::EndPin();
|
||||
}
|
||||
ImGui::EndGroup();
|
||||
|
||||
if (ni == 0) {
|
||||
// Gen: no inputs, push a small placeholder so layout is consistent
|
||||
ImGui::Dummy(ImVec2(4, 4));
|
||||
}
|
||||
ImGui::SameLine();
|
||||
|
||||
// Controls
|
||||
ImGui::BeginGroup(); // controls column
|
||||
ImGui::PushID(static_cast<int>(step.editor_uid));
|
||||
if (def->controls.empty() && def->kind != DagKind::Output) {
|
||||
ImGui::Dummy(ImVec2(60, PIN_DIAMETER));
|
||||
}
|
||||
for (size_t ci = 0; ci < def->controls.size(); ++ci) {
|
||||
const DagControl& ctrl = def->controls[ci];
|
||||
ImGui::SetNextItemWidth(150.0f);
|
||||
@@ -263,9 +283,6 @@ bool dag_node_editor(std::vector<DagStep>& pipeline) {
|
||||
} else if (ctrl.kind == DagControl::Kind::Color) {
|
||||
int pr = ctrl.param_idx[0];
|
||||
if (pr >= 0 && pr + 2 < 4) {
|
||||
// Suspend the node editor while the color picker popup is
|
||||
// open so its clicks don't reach the canvas (and so the
|
||||
// popup isn't clipped to the node bounds).
|
||||
ed::Suspend();
|
||||
ImGui::ColorEdit3(uid_lbl, &step.params[static_cast<size_t>(pr)],
|
||||
ImGuiColorEditFlags_NoInputs |
|
||||
@@ -276,14 +293,21 @@ bool dag_node_editor(std::vector<DagStep>& pipeline) {
|
||||
}
|
||||
}
|
||||
ImGui::PopID();
|
||||
ImGui::EndGroup();
|
||||
|
||||
// Output pin (skip for the terminal Output node — it has no output)
|
||||
if (def->kind != DagKind::Output) {
|
||||
ImGui::Dummy(ImVec2(0, 2));
|
||||
ImGui::SameLine();
|
||||
|
||||
ImGui::BeginGroup(); // output column
|
||||
if (has_output_pin) {
|
||||
ed::BeginPin(ed::PinId(output_pin_id(step.editor_uid)), ed::PinKind::Output);
|
||||
ImGui::Text("out ->");
|
||||
ed::PinPivotAlignment(ImVec2(1.0f, 0.5f));
|
||||
ed::PinPivotSize(ImVec2(0, 0));
|
||||
draw_pin_circle(col);
|
||||
ed::EndPin();
|
||||
} else {
|
||||
ImGui::Dummy(ImVec2(PIN_DIAMETER, PIN_DIAMETER));
|
||||
}
|
||||
ImGui::EndGroup();
|
||||
|
||||
ed::EndNode();
|
||||
}
|
||||
@@ -298,13 +322,33 @@ bool dag_node_editor(std::vector<DagStep>& pipeline) {
|
||||
if (sid.empty()) continue;
|
||||
int src_idx = find_by_id(pipeline, sid);
|
||||
if (src_idx < 0) continue;
|
||||
uint32_t src_uid = pipeline[static_cast<size_t>(src_idx)].editor_uid;
|
||||
ed::Link(ed::LinkId(link_id(src_uid, step.editor_uid, k)),
|
||||
ed::PinId(output_pin_id(src_uid)),
|
||||
ed::PinId(input_pin_id(step.editor_uid, k)));
|
||||
const DagStep& src_step = pipeline[static_cast<size_t>(src_idx)];
|
||||
const DagNodeDef* src_def = dag_find(src_step.name);
|
||||
ImVec4 link_col = src_def ? kind_color(src_def->kind) : ImVec4(0.7f, 0.7f, 0.7f, 1.0f);
|
||||
ed::Link(ed::LinkId(link_id(src_step.editor_uid, step.editor_uid, k)),
|
||||
ed::PinId(output_pin_id(src_step.editor_uid)),
|
||||
ed::PinId(input_pin_id(step.editor_uid, k)),
|
||||
link_col, 2.5f);
|
||||
}
|
||||
}
|
||||
|
||||
// ── Right-click on a link deletes it directly ──────────────────────────
|
||||
{
|
||||
ed::Suspend();
|
||||
ed::LinkId ctx_lid;
|
||||
if (ed::ShowLinkContextMenu(&ctx_lid)) {
|
||||
uintptr_t raw = ctx_lid.Get();
|
||||
uint32_t to_uid = static_cast<uint32_t>((raw >> 8) & 0xFFFu);
|
||||
int slot = static_cast<int>(raw & 0xFFu);
|
||||
int to_idx = find_by_uid(pipeline, to_uid);
|
||||
if (to_idx >= 0 && slot >= 0 && slot < 4) {
|
||||
pipeline[static_cast<size_t>(to_idx)].source_ids[static_cast<size_t>(slot)].clear();
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
ed::Resume();
|
||||
}
|
||||
|
||||
// ── Handle link creation ─────────────────────────────────────────────────
|
||||
if (ed::BeginCreate()) {
|
||||
ed::PinId start_pin, end_pin;
|
||||
|
||||
Reference in New Issue
Block a user