| Line | Branch | Exec | Source |
|---|---|---|---|
| 1 | #include "node.h" | ||
| 2 | #include "bezier.h" | ||
| 3 | |||
| 4 | #include "imgui_internal.h" | ||
| 5 | |||
| 6 | #include <algorithm> | ||
| 7 | |||
| 8 | void GraphEditor::draw() { | ||
| 9 | ✗ | hovered_link = nullptr; | |
| 10 | ✗ | hovered_pin = nullptr; | |
| 11 | |||
| 12 | ✗ | ImGuiIO& io = ImGui::GetIO(); | |
| 13 | |||
| 14 | // ImGui::SetNextWindowSize(ImVec2(700, 600), ImGuiCond_FirstUseEver); | ||
| 15 | #ifdef IMGUI_HAS_VIEWPORT | ||
| 16 | ImGuiViewport* viewport = ImGui::GetMainViewport(); | ||
| 17 | ImGui::SetNextWindowPos(viewport->GetWorkPos()); | ||
| 18 | ImGui::SetNextWindowSize(viewport->GetWorkSize()); | ||
| 19 | ImGui::SetNextWindowViewport(viewport->ID); | ||
| 20 | #else | ||
| 21 | ✗ | ImGui::SetNextWindowPos(ImVec2(0.0f, 0.0f)); | |
| 22 | ✗ | ImGui::SetNextWindowSize(ImGui::GetIO().DisplaySize); | |
| 23 | #endif | ||
| 24 | ✗ | ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 0.0f); | |
| 25 | ✗ | ImGui::Begin("GraphEditor", nullptr, ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoResize); | |
| 26 | |||
| 27 | ✗ | const ImVec2 offset = ImGui::GetCursorScreenPos() + scrolling; | |
| 28 | ✗ | _offset = offset; | |
| 29 | |||
| 30 | ✗ | ImGui::BeginGroup(); | |
| 31 | ✗ | ImGui::BeginChild("scrolling_region", | |
| 32 | ✗ | ImVec2(0, 0), | |
| 33 | true, | ||
| 34 | ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoMove); | ||
| 35 | |||
| 36 | ✗ | ImDrawList* draw_list = ImGui::GetWindowDrawList(); | |
| 37 | |||
| 38 | ✗ | drawgrid(); | |
| 39 | |||
| 40 | ✗ | ImGui::PushItemWidth(120.0f); | |
| 41 | ✗ | draw_list->ChannelsSplit(3); | |
| 42 | ✗ | for (Forest& forest: forests) { | |
| 43 | ✗ | for (Tree& tree: forest.trees) { | |
| 44 | ✗ | current_tree = &tree; | |
| 45 | |||
| 46 | ✗ | for (Node& node: tree.nodes) { | |
| 47 | ✗ | draw(&node, offset); | |
| 48 | } | ||
| 49 | |||
| 50 | ✗ | draw_list->ChannelsSetCurrent(0); | |
| 51 | ✗ | for (Link& link: tree.links) { | |
| 52 | ✗ | draw(&link, offset); | |
| 53 | } | ||
| 54 | |||
| 55 | ✗ | current_tree = nullptr; | |
| 56 | } | ||
| 57 | } | ||
| 58 | ✗ | draw_list->ChannelsMerge(); | |
| 59 | |||
| 60 | ✗ | if (ImGui::IsWindowHovered() && !ImGui::IsAnyItemActive() && | |
| 61 | ✗ | ImGui::IsMouseDragging(ImGuiMouseButton_Middle, 0.0f)) | |
| 62 | ✗ | scrolling = scrolling + io.MouseDelta; | |
| 63 | |||
| 64 | ✗ | handle_events(offset); | |
| 65 | |||
| 66 | ✗ | ImGui::PopItemWidth(); | |
| 67 | ✗ | ImGui::EndChild(); | |
| 68 | ✗ | ImGui::EndGroup(); | |
| 69 | ✗ | ImGui::End(); | |
| 70 | |||
| 71 | ✗ | ImGui::PopStyleVar(1); | |
| 72 | ✗ | } | |
| 73 | |||
| 74 | void GraphEditor::handle_events(ImVec2 offset) { | ||
| 75 | ✗ | ImDrawList* draw_list = ImGui::GetWindowDrawList(); | |
| 76 | |||
| 77 | // REMOVE LINK | ||
| 78 | ✗ | if (ImGui::IsKeyDown(ImGuiKey_LeftAlt) && ImGui::IsMouseDown(ImGuiMouseButton_Left)) { | |
| 79 | ✗ | if (hovered_link != nullptr) { | |
| 80 | ✗ | auto it = std::remove_if(std::begin(selected_tree->links), | |
| 81 | ✗ | std::end(selected_tree->links), | |
| 82 | [=](Link const& link) { | ||
| 83 | ✗ | Link const* l = &link; | |
| 84 | ✗ | return l == hovered_link; | |
| 85 | }); | ||
| 86 | |||
| 87 | ✗ | it->from->connected = false; | |
| 88 | ✗ | it->to->connected = false; | |
| 89 | |||
| 90 | ✗ | selected_tree->links.erase(it); | |
| 91 | |||
| 92 | ✗ | hovered_link = nullptr; | |
| 93 | ✗ | selected_tree = nullptr; | |
| 94 | } | ||
| 95 | |||
| 96 | ✗ | return; | |
| 97 | } | ||
| 98 | |||
| 99 | // CREATE LINK | ||
| 100 | ✗ | if (ImGui::IsMouseReleased(ImGuiMouseButton_Left)) { | |
| 101 | ✗ | if (selected_pin != nullptr && hovered_pin != nullptr) { | |
| 102 | ✗ | selected_tree->links.emplace_back(selected_pin, hovered_pin); | |
| 103 | } | ||
| 104 | ✗ | printf("Selected: %d, hovered %d, selected_tree %d\n", | |
| 105 | ✗ | selected_pin != nullptr, | |
| 106 | ✗ | hovered_pin != nullptr, | |
| 107 | ✗ | selected_tree != nullptr); | |
| 108 | ✗ | selected_pin = nullptr; | |
| 109 | ✗ | hovered_pin = nullptr; | |
| 110 | ✗ | selected_tree = nullptr; | |
| 111 | |||
| 112 | ✗ | selected_node = nullptr; | |
| 113 | ✗ | rectangle_select = false; | |
| 114 | ✗ | rectangle_selection = ImRect(); | |
| 115 | ✗ | return; | |
| 116 | } | ||
| 117 | |||
| 118 | // PENDING LINK | ||
| 119 | ✗ | if (ImGui::IsMouseDown(ImGuiMouseButton_Left)) { | |
| 120 | ✗ | if (selected_pin != nullptr) { | |
| 121 | ✗ | auto color = _colors[selected_pin->type]; | |
| 122 | ✗ | draw_bezier(draw_list, | |
| 123 | ✗ | with_scroll(selected_pin->pos), | |
| 124 | ImGui::GetMousePos(), | ||
| 125 | color, | ||
| 126 | bezier_segments, | ||
| 127 | tickness); | ||
| 128 | ✗ | return; | |
| 129 | } | ||
| 130 | |||
| 131 | ✗ | if (selected_node == nullptr) { | |
| 132 | ✗ | if (!rectangle_select) { | |
| 133 | ✗ | rectangle_select = true; | |
| 134 | ✗ | for (auto& item: selected) { | |
| 135 | ✗ | item.first->selected = false; | |
| 136 | } | ||
| 137 | ✗ | selected.clear(); | |
| 138 | ✗ | rectangle_start = ImGui::GetMousePos(); | |
| 139 | } | ||
| 140 | |||
| 141 | // ImVec2 mp = ImGui::GetMousePos(); | ||
| 142 | // ImVec2 mn = ImVec2(std::min(rectangle_start.x, mp.x), std::min(rectangle_start.y, | ||
| 143 | // mp.y)); ImVec2 mx = ImVec2(std::max(rectangle_start.x, mp.x), | ||
| 144 | // std::max(rectangle_start.y, mp.y)); rectangle_selection = ImRect(mn, mx); | ||
| 145 | |||
| 146 | ✗ | rectangle_selection = ImRect(rectangle_start, rectangle_start); | |
| 147 | ✗ | rectangle_selection.Add(ImGui::GetMousePos()); | |
| 148 | |||
| 149 | ✗ | draw_list->AddRectFilled( | |
| 150 | ✗ | rectangle_selection.GetTR(), rectangle_selection.GetBL(), rectangle_color); | |
| 151 | ✗ | return; | |
| 152 | } | ||
| 153 | } | ||
| 154 | } | ||
| 155 | |||
| 156 | void GraphEditor::drawgrid() { | ||
| 157 | ✗ | ImDrawList* draw_list = ImGui::GetWindowDrawList(); | |
| 158 | |||
| 159 | // Display grid | ||
| 160 | ✗ | if (show_grid) { | |
| 161 | ✗ | ImU32 GRID_COLOR = IM_COL32(200, 200, 200, 40); | |
| 162 | ✗ | float GRID_SZ = 64.0f; | |
| 163 | ✗ | ImVec2 win_pos = ImGui::GetCursorScreenPos(); | |
| 164 | ✗ | ImVec2 canvas_sz = ImGui::GetWindowSize(); | |
| 165 | |||
| 166 | ✗ | for (float x = fmodf(scrolling.x, GRID_SZ); x < canvas_sz.x; x += GRID_SZ) | |
| 167 | ✗ | draw_list->AddLine( | |
| 168 | ✗ | ImVec2(x, 0.0f) + win_pos, ImVec2(x, canvas_sz.y) + win_pos, GRID_COLOR); | |
| 169 | |||
| 170 | ✗ | for (float y = fmodf(scrolling.y, GRID_SZ); y < canvas_sz.y; y += GRID_SZ) | |
| 171 | ✗ | draw_list->AddLine( | |
| 172 | ✗ | ImVec2(0.0f, y) + win_pos, ImVec2(canvas_sz.x, y) + win_pos, GRID_COLOR); | |
| 173 | } | ||
| 174 | ✗ | } | |
| 175 | |||
| 176 | uint64_t nextid() { | ||
| 177 | static uint64_t counter = 0; | ||
| 178 | ✗ | return counter++; | |
| 179 | } | ||
| 180 | |||
| 181 | bool draw_bezier(ImDrawList* draw_list, | ||
| 182 | ImVec2 p1, | ||
| 183 | ImVec2 p2, | ||
| 184 | ImU32 color, | ||
| 185 | int segments, | ||
| 186 | float tickness, | ||
| 187 | float eps) { | ||
| 188 | ✗ | if (p1.x > p2.x) { | |
| 189 | ✗ | std::swap(p1, p2); | |
| 190 | } | ||
| 191 | |||
| 192 | auto sgn = [](float v) -> int { return int(v > 0); }; | ||
| 193 | |||
| 194 | ✗ | auto offset = ImVec2((p2.x - p1.x + 1) / 2.f, 0); | |
| 195 | ✗ | auto y_offset = sgn(p2.y - p1.y) * (p2.y - p1.y) / 2.f; | |
| 196 | |||
| 197 | ✗ | if (p2.y - p1.y > p2.x - p1.x) { | |
| 198 | ✗ | offset = ImVec2(0, y_offset); | |
| 199 | } | ||
| 200 | |||
| 201 | ✗ | ImVec2 P0 = p1; | |
| 202 | ✗ | ImVec2 P1 = p1 + offset; | |
| 203 | ✗ | ImVec2 P2 = p2 - offset; | |
| 204 | ✗ | ImVec2 P3 = p2; | |
| 205 | |||
| 206 | const ImProjectResult result = | ||
| 207 | ✗ | ImProjectOnCubicBezier(ImGui::GetMousePos(), P0, P1, P2, P3, segments); | |
| 208 | |||
| 209 | ✗ | bool hovered = result.Distance <= tickness + eps; | |
| 210 | |||
| 211 | ✗ | draw_list->AddBezierCubic(P0, P1, P2, P3, color, tickness + float(hovered), segments); | |
| 212 | |||
| 213 | ✗ | return hovered; | |
| 214 | } | ||
| 215 | |||
| 216 | void GraphEditor::draw(Link* link, ImVec2 offset) { | ||
| 217 | ✗ | auto color = _colors[link->from->type]; | |
| 218 | |||
| 219 | ✗ | ImDrawList* draw_list = ImGui::GetWindowDrawList(); | |
| 220 | ✗ | bool hovered = draw_bezier(draw_list, | |
| 221 | ✗ | with_scroll(link->from->pos), | |
| 222 | ✗ | with_scroll(link->to->pos), | |
| 223 | color, | ||
| 224 | bezier_segments, | ||
| 225 | tickness); | ||
| 226 | |||
| 227 | ✗ | if (hovered) { | |
| 228 | ✗ | hovered_link = link; | |
| 229 | ✗ | selected_tree = current_tree; | |
| 230 | } | ||
| 231 | ✗ | } | |
| 232 | |||
| 233 | ImVec2 GraphEditor::estimate_size(Node& node, ImVec2 font_size) { | ||
| 234 | ✗ | ImRect size; | |
| 235 | ✗ | float height = 0; | |
| 236 | ✗ | for (int i = 0; i < node.inputs.size(); i++) { | |
| 237 | ✗ | if (node.inputs[i].name.empty()) | |
| 238 | ✗ | continue; | |
| 239 | |||
| 240 | ✗ | ImVec2 v = font_size * ImVec2(node.inputs[i].name.size(), 1); | |
| 241 | |||
| 242 | ✗ | height += v.y; | |
| 243 | ✗ | size.Add(v + ImVec2(pin_radius * 4, height)); | |
| 244 | } | ||
| 245 | |||
| 246 | ✗ | ImVec2 v = font_size * ImVec2(node.name.size(), 1); | |
| 247 | ✗ | size.Add(v + ImVec2(pin_radius * 4, height)); | |
| 248 | |||
| 249 | ✗ | ImVec2 col1 = size.GetSize(); | |
| 250 | |||
| 251 | ✗ | height = 0; | |
| 252 | ✗ | for (int i = 0; i < node.outputs.size(); i++) { | |
| 253 | ✗ | if (node.outputs[i].name.empty()) | |
| 254 | ✗ | continue; | |
| 255 | |||
| 256 | ✗ | ImVec2 v = font_size * ImVec2(node.outputs[i].name.size(), 1); | |
| 257 | ✗ | height += v.y; | |
| 258 | ✗ | size.Add(v + ImVec2(col1.x, height) + ImVec2(pin_radius * 2, 0)); | |
| 259 | } | ||
| 260 | |||
| 261 | ✗ | return size.GetSize(); | |
| 262 | } | ||
| 263 | |||
| 264 | void GraphEditor::draw(Node* node, ImVec2 offset) { | ||
| 265 | ✗ | ImDrawList* draw_list = ImGui::GetWindowDrawList(); | |
| 266 | ✗ | ImGuiIO& io = ImGui::GetIO(); | |
| 267 | |||
| 268 | ✗ | ImGui::PushID(int(node->id)); | |
| 269 | |||
| 270 | ✗ | ImVec2 node_rect_min = offset + node->pos; // Parent offset | |
| 271 | static float value; | ||
| 272 | ✗ | bool old_any_active = ImGui::IsAnyItemActive(); | |
| 273 | |||
| 274 | // Content | ||
| 275 | ✗ | draw_list->ChannelsSetCurrent(2); | |
| 276 | // ImGui::SetCursorScreenPos(node_rect_min + ImVec2(1, 1) * 0* node_padding); | ||
| 277 | |||
| 278 | |||
| 279 | ✗ | ImGui::BeginGroup(); | |
| 280 | |||
| 281 | // Argument Column | ||
| 282 | ✗ | ImGui::BeginGroup(); | |
| 283 | ✗ | ImVec2 txt = ImGui::CalcTextSize("T"); | |
| 284 | ✗ | float line_height = node->layout.input.y; | |
| 285 | ✗ | float line_width = node->layout.input.x; | |
| 286 | ✗ | ImVec2 start = node->pos + ImVec2(1, 1) * pin_radius + ImVec2(1, 1) * node_padding; | |
| 287 | ✗ | float new_width = pin_radius / 2; | |
| 288 | ✗ | float new_height = std::max(pin_radius * 2, txt.y); | |
| 289 | |||
| 290 | ✗ | ImGui::SetCursorPos(start); | |
| 291 | ✗ | _size = ImRect(); | |
| 292 | |||
| 293 | // Inputs | ||
| 294 | ✗ | for (int slot_idx = 0; slot_idx < node->inputs.size(); slot_idx++) { | |
| 295 | ✗ | Pin& pin = node->inputs[slot_idx]; | |
| 296 | |||
| 297 | ✗ | pin.pos = start; | |
| 298 | |||
| 299 | ✗ | const char* name = pin.name.c_str(); | |
| 300 | ✗ | if (pin.kind == PinKind::Flow) { | |
| 301 | ✗ | name = node->name.c_str(); | |
| 302 | } | ||
| 303 | ✗ | ImVec2 v = ImGui::CalcTextSize(name); | |
| 304 | |||
| 305 | ✗ | draw(&pin, with_scroll(pin.pos)); | |
| 306 | ✗ | ImGui::SetCursorPos( | |
| 307 | ✗ | with_scroll(pin.pos + ImVec2(pin_radius / 2 + pin_label_margin, -(pin_radius + v.y / 4)))); | |
| 308 | |||
| 309 | ✗ | ImGui::Text("%s", name); | |
| 310 | ✗ | ImVec2 s(0, 0); | |
| 311 | ✗ | if (pin.type == PinType::Float && !pin.connected) { | |
| 312 | ✗ | ImGui::SameLine(); | |
| 313 | ✗ | ImGui::PushItemWidth(txt.x * 5); | |
| 314 | ✗ | ImGui::InputFloat("", pin.as_float(), 0, 0, "%.f"); | |
| 315 | ✗ | ImGui::PopItemWidth(); | |
| 316 | |||
| 317 | ✗ | s = ImGui::GetItemRectSize(); | |
| 318 | } | ||
| 319 | |||
| 320 | ✗ | new_height = std::max(new_height, v.y); | |
| 321 | ✗ | new_width = std::max(new_width, v.x + s.x); | |
| 322 | ✗ | start.y += line_height; | |
| 323 | } | ||
| 324 | ✗ | node->layout.input.y = new_height; | |
| 325 | ✗ | node->layout.input.x = new_width + pin_radius; | |
| 326 | |||
| 327 | ✗ | ImGui::EndGroup(); | |
| 328 | ✗ | ImVec2 s = ImGui::GetItemRectSize(); | |
| 329 | |||
| 330 | // | ||
| 331 | ✗ | if (node->exec_in() == nullptr) { | |
| 332 | ✗ | const char* name = node->name.c_str(); | |
| 333 | |||
| 334 | ✗ | ImVec2 sz = ImGui::CalcTextSize(name) + ImVec2(txt.x, 0); | |
| 335 | // ImVec2 sz(txt.x * (node->name.size() + 2), txt.y); | ||
| 336 | |||
| 337 | // ImVec2 center = ImVec2( | ||
| 338 | // node->layout.input.x, | ||
| 339 | // (node->size.y - sz.y )/ 2.f | ||
| 340 | // ); | ||
| 341 | |||
| 342 | ✗ | ImVec2 center = node->size / 2 + ImVec2(-sz.x, -sz.y - node_padding); | |
| 343 | ✗ | ImGui::SetCursorPos(with_scroll(node->pos + center)); | |
| 344 | ✗ | ImGui::Text("%s", name); | |
| 345 | |||
| 346 | ✗ | node->layout.input.x = new_width + sz.x + 2 * pin_radius; | |
| 347 | } | ||
| 348 | |||
| 349 | // Ouputs | ||
| 350 | ✗ | ImGui::SameLine(); | |
| 351 | ✗ | ImGui::BeginGroup(); | |
| 352 | // Output Column | ||
| 353 | // ImGui::SetCursorScreenPos(node_rect_min + ImVec2(node->layout.input.x, 0)); | ||
| 354 | |||
| 355 | ✗ | ImGui::SetCursorPos(node->pos + ImVec2(1, 0) * pin_radius + ImVec2(node->layout.input.x, 0) + | |
| 356 | ✗ | ImVec2(0, 1) * node_padding); | |
| 357 | ✗ | start = ImGui::GetCursorPos(); | |
| 358 | |||
| 359 | ✗ | line_width = node->layout.output.x; | |
| 360 | ✗ | new_width = pin_radius; | |
| 361 | ✗ | new_height = pin_radius * 2; | |
| 362 | |||
| 363 | ✗ | for (int slot_idx = 0; slot_idx < node->outputs.size(); slot_idx++) { | |
| 364 | ✗ | Pin& pin = node->outputs[slot_idx]; | |
| 365 | ✗ | pin.pos = start + ImVec2(pin_radius / 2, 0); | |
| 366 | ✗ | const char* name = pin.name.c_str(); | |
| 367 | |||
| 368 | ✗ | ImVec2 v = ImGui::CalcTextSize(name); | |
| 369 | ✗ | if (pin.name.empty()) | |
| 370 | ✗ | v.y = 0; | |
| 371 | |||
| 372 | ✗ | new_width = std::max(new_width, v.x + pin_radius); | |
| 373 | ✗ | new_height = std::max(new_height, v.y); | |
| 374 | ✗ | float s = line_width - v.x - pin_radius / 2; | |
| 375 | |||
| 376 | ✗ | ImGui::SetCursorPos(with_scroll(start + ImVec2(s, -v.y / 4))); | |
| 377 | ✗ | ImGui::Text("%s", name); | |
| 378 | |||
| 379 | ✗ | pin.pos = start + ImVec2(line_width + pin_label_margin, 0) + ImVec2(1, 1) * pin_radius + | |
| 380 | ✗ | ImVec2(0, 0); | |
| 381 | ✗ | draw(&pin, with_scroll(pin.pos)); | |
| 382 | |||
| 383 | ✗ | start.y += line_height; | |
| 384 | } | ||
| 385 | ✗ | node->layout.output.x = new_width + pin_label_margin; | |
| 386 | ✗ | node->layout.output.y = new_height; | |
| 387 | ✗ | ImGui::EndGroup(); | |
| 388 | ✗ | ImGui::EndGroup(); | |
| 389 | // --- | ||
| 390 | |||
| 391 | ✗ | node->size = (_size.GetSize() - node->pos) + ImVec2(1, 1) * node_padding; | |
| 392 | ✗ | ImVec2 node_rect_max = node_rect_min + node->size; | |
| 393 | |||
| 394 | // Background | ||
| 395 | ✗ | draw_list->ChannelsSetCurrent(1); | |
| 396 | // ImGui::SetCursorScreenPos(node_rect_min); | ||
| 397 | |||
| 398 | ✗ | ImGui::SetCursorPos(with_scroll(node->pos)); | |
| 399 | ✗ | ImGui::InvisibleButton("node", node->size); | |
| 400 | /// --- | ||
| 401 | |||
| 402 | // Events | ||
| 403 | ✗ | if (ImGui::IsItemHovered()) { | |
| 404 | ✗ | hovered_node = node; | |
| 405 | ✗ | open_context_menu |= ImGui::IsMouseClicked(1); | |
| 406 | } | ||
| 407 | |||
| 408 | ✗ | bool node_widgets_active = (!old_any_active && ImGui::IsAnyItemActive()); | |
| 409 | ✗ | bool node_moving_active = ImGui::IsItemActive(); | |
| 410 | ✗ | if (node_widgets_active || node_moving_active) | |
| 411 | ✗ | selected_node = node; | |
| 412 | |||
| 413 | ✗ | if (node_moving_active && ImGui::IsMouseDragging(ImGuiMouseButton_Left)) { | |
| 414 | |||
| 415 | ✗ | if (!has_selection()) { | |
| 416 | ✗ | node->pos = node->pos + io.MouseDelta; | |
| 417 | } else { | ||
| 418 | ✗ | for (auto& item: selected) { | |
| 419 | ✗ | item.first->pos = item.first->pos + io.MouseDelta; | |
| 420 | } | ||
| 421 | } | ||
| 422 | } | ||
| 423 | |||
| 424 | // ---- | ||
| 425 | |||
| 426 | ✗ | draw_list->AddRectFilled(node_rect_min, node_rect_max, node_bg_color, 4.0f); | |
| 427 | ✗ | draw_list->AddRect(node_rect_min, | |
| 428 | node_rect_max, | ||
| 429 | ✗ | node->selected ? node_selected_color : node_outline_color, | |
| 430 | 4.0f, | ||
| 431 | 0, | ||
| 432 | ✗ | node->selected ? 4.0f : 1.0f); | |
| 433 | ✗ | check_selected(node); | |
| 434 | |||
| 435 | ✗ | ImGui::PopID(); | |
| 436 | ✗ | } | |
| 437 | |||
| 438 | void GraphEditor::draw(Pin* pin, ImVec2 center) { | ||
| 439 | ✗ | ImDrawList* draw_list = ImGui::GetWindowDrawList(); | |
| 440 | ✗ | draw_list->ChannelsSetCurrent(2); | |
| 441 | |||
| 442 | ✗ | ImGui::PushID(int(pin->id)); | |
| 443 | |||
| 444 | ✗ | ImVec2 radius = ImVec2(1, 1) * pin_radius; | |
| 445 | ✗ | bool hovered = false; | |
| 446 | ✗ | bool held = false; | |
| 447 | ✗ | auto flags = ImGuiButtonFlags_NoHoldingActiveId; | |
| 448 | |||
| 449 | ✗ | ImVec2 pos = center - radius - get_offset(); | |
| 450 | ✗ | ImVec2 size = radius * 2; | |
| 451 | ✗ | ImRect bb(pos, pos + size); | |
| 452 | |||
| 453 | ✗ | ImGui::SetCursorPos(with_scroll(pos)); | |
| 454 | ✗ | ImGui::ItemSize(size, 10); | |
| 455 | ✗ | ImGui::ItemAdd(bb, int(pin->id)); | |
| 456 | ✗ | ImGui::ButtonBehavior( // | |
| 457 | ✗ | ImRect(center - radius, center + radius), // | |
| 458 | ✗ | int(pin->id), // | |
| 459 | &hovered, // | ||
| 460 | &held, // | ||
| 461 | flags // | ||
| 462 | ); | ||
| 463 | |||
| 464 | // ButtonBehavior hovered does not work well | ||
| 465 | ✗ | hovered = bb.Contains(ImGui::GetMousePos()); | |
| 466 | |||
| 467 | // ImGui::SetCursorPos(with_scroll(pos)); | ||
| 468 | // ImGui::Button("", size); | ||
| 469 | |||
| 470 | ✗ | _size.Add(pos); | |
| 471 | ✗ | _size.Add(pos + size); | |
| 472 | |||
| 473 | ✗ | if (held) { | |
| 474 | ✗ | selected_pin = pin; | |
| 475 | ✗ | selected_tree = current_tree; | |
| 476 | } | ||
| 477 | |||
| 478 | ✗ | if (hovered) { | |
| 479 | ✗ | hovered_pin = pin; | |
| 480 | } | ||
| 481 | |||
| 482 | ✗ | ImU32 color = _colors[pin->type]; | |
| 483 | |||
| 484 | auto style = PinStyle{ | ||
| 485 | // | ||
| 486 | ✗ | pin->kind, // | |
| 487 | ✗ | pin->connected, // | |
| 488 | color, // | ||
| 489 | color // | ||
| 490 | ✗ | }; | |
| 491 | |||
| 492 | ✗ | draw(style, center - radius, radius * 2.0); | |
| 493 | ✗ | ImGui::PopID(); | |
| 494 | ✗ | } | |
| 495 | |||
| 496 | void GraphEditor::draw(PinStyle& style, ImVec2 pos, ImVec2 size) { | ||
| 497 | |||
| 498 | ✗ | switch (style.kind) { | |
| 499 | ✗ | case PinKind::Flow: return draw_flow(style, pos, size); | |
| 500 | ✗ | case PinKind::Circle: return draw_circle(style, pos, size); | |
| 501 | ✗ | case PinKind::Square: return draw_square(style, pos, size); | |
| 502 | ✗ | case PinKind::Grid: return draw_grid(style, pos, size); | |
| 503 | ✗ | case PinKind::RoundSquare: return draw_round_square(style, pos, size); | |
| 504 | ✗ | case PinKind::Diamond: return draw_diamond(style, pos, size); | |
| 505 | } | ||
| 506 | |||
| 507 | ✗ | return draw_circle(style, pos, size); | |
| 508 | } | ||
| 509 | |||
| 510 | void GraphEditor::draw_flow(PinStyle& style, ImVec2 pos, ImVec2 size) { | ||
| 511 | ✗ | ImDrawList* draw_list = ImGui::GetWindowDrawList(); | |
| 512 | |||
| 513 | ✗ | auto rect = ImRect(pos, pos + size); | |
| 514 | ✗ | auto rect_x = rect.Min.x; | |
| 515 | ✗ | auto rect_y = rect.Min.y; | |
| 516 | ✗ | auto rect_w = rect.Max.x - rect.Min.x; | |
| 517 | ✗ | auto rect_h = rect.Max.y - rect.Min.y; | |
| 518 | ✗ | auto rect_center_x = (rect.Min.x + rect.Max.x) * 0.5f; | |
| 519 | ✗ | auto rect_center_y = (rect.Min.y + rect.Max.y) * 0.5f; | |
| 520 | ✗ | auto rect_center = ImVec2(rect_center_x, rect_center_y); | |
| 521 | ✗ | const auto outline_scale = rect_w / 24.0f; | |
| 522 | ✗ | const auto extra_segments = static_cast<int>(2 * outline_scale); // for full circle | |
| 523 | |||
| 524 | ✗ | const auto origin_scale = rect_w / 24.0f; | |
| 525 | ✗ | const auto offset_x = 1.0f * origin_scale; | |
| 526 | ✗ | const auto offset_y = 0.0f * origin_scale; | |
| 527 | ✗ | const auto margin = (style.filled ? 2.0f : 2.0f) * origin_scale; | |
| 528 | ✗ | const auto rounding = 0.1f * origin_scale; | |
| 529 | ✗ | const auto tip_round = 0.7f; // percentage of triangle edge (for tip) | |
| 530 | // const auto edge_round = 0.7f; // percentage of triangle edge (for corner) | ||
| 531 | ✗ | const auto canvas = ImRect(rect.Min.x + margin + offset_x, | |
| 532 | ✗ | rect.Min.y + margin + offset_y, | |
| 533 | ✗ | rect.Max.x - margin + offset_x, | |
| 534 | ✗ | rect.Max.y - margin + offset_y); | |
| 535 | ✗ | const auto canvas_x = canvas.Min.x; | |
| 536 | ✗ | const auto canvas_y = canvas.Min.y; | |
| 537 | ✗ | const auto canvas_w = canvas.Max.x - canvas.Min.x; | |
| 538 | ✗ | const auto canvas_h = canvas.Max.y - canvas.Min.y; | |
| 539 | |||
| 540 | ✗ | const auto left = canvas_x + canvas_w * 0.5f * 0.3f; | |
| 541 | ✗ | const auto right = canvas_x + canvas_w - canvas_w * 0.5f * 0.3f; | |
| 542 | ✗ | const auto top = canvas_y + canvas_h * 0.5f * 0.2f; | |
| 543 | ✗ | const auto bottom = canvas_y + canvas_h - canvas_h * 0.5f * 0.2f; | |
| 544 | ✗ | const auto center_y = (top + bottom) * 0.5f; | |
| 545 | // const auto angle = AX_PI * 0.5f * 0.5f * 0.5f; | ||
| 546 | |||
| 547 | ✗ | const auto tip_top = ImVec2(canvas_x + canvas_w * 0.5f, top); | |
| 548 | ✗ | const auto tip_right = ImVec2(right, center_y); | |
| 549 | ✗ | const auto tip_bottom = ImVec2(canvas_x + canvas_w * 0.5f, bottom); | |
| 550 | |||
| 551 | ✗ | draw_list->PathLineTo(ImVec2(left, top) + ImVec2(0, rounding)); | |
| 552 | ✗ | draw_list->PathBezierCubicCurveTo( | |
| 553 | ✗ | ImVec2(left, top), ImVec2(left, top), ImVec2(left, top) + ImVec2(rounding, 0)); | |
| 554 | ✗ | draw_list->PathLineTo(tip_top); | |
| 555 | ✗ | draw_list->PathLineTo(tip_top + (tip_right - tip_top) * tip_round); | |
| 556 | ✗ | draw_list->PathBezierCubicCurveTo( | |
| 557 | ✗ | tip_right, tip_right, tip_bottom + (tip_right - tip_bottom) * tip_round); | |
| 558 | ✗ | draw_list->PathLineTo(tip_bottom); | |
| 559 | ✗ | draw_list->PathLineTo(ImVec2(left, bottom) + ImVec2(rounding, 0)); | |
| 560 | ✗ | draw_list->PathBezierCubicCurveTo( | |
| 561 | ✗ | ImVec2(left, bottom), ImVec2(left, bottom), ImVec2(left, bottom) - ImVec2(0, rounding)); | |
| 562 | |||
| 563 | ✗ | if (style.filled) { | |
| 564 | ✗ | if (style.fill & 0xFF000000) { | |
| 565 | ✗ | draw_list->AddConvexPolyFilled( | |
| 566 | ✗ | draw_list->_Path.Data, draw_list->_Path.Size, style.fill); | |
| 567 | } | ||
| 568 | } | ||
| 569 | ✗ | draw_list->PathStroke(style.color, true, 2.0f * outline_scale); | |
| 570 | ✗ | } | |
| 571 | |||
| 572 | void GraphEditor::draw_circle(PinStyle& style, ImVec2 pos, ImVec2 size) { | ||
| 573 | ✗ | ImDrawList* draw_list = ImGui::GetWindowDrawList(); | |
| 574 | |||
| 575 | ✗ | auto rect = ImRect(pos, pos + size); | |
| 576 | ✗ | auto rect_x = rect.Min.x; | |
| 577 | ✗ | auto rect_y = rect.Min.y; | |
| 578 | ✗ | auto rect_w = rect.Max.x - rect.Min.x; | |
| 579 | ✗ | auto rect_h = rect.Max.y - rect.Min.y; | |
| 580 | ✗ | auto rect_center_x = (rect.Min.x + rect.Max.x) * 0.5f; | |
| 581 | ✗ | auto rect_center_y = (rect.Min.y + rect.Max.y) * 0.5f; | |
| 582 | ✗ | auto rect_center = ImVec2(rect_center_x, rect_center_y); | |
| 583 | ✗ | const auto outline_scale = rect_w / 24.0f; | |
| 584 | ✗ | const auto extra_segments = static_cast<int>(2 * outline_scale); // for full circle | |
| 585 | |||
| 586 | ✗ | auto triangleStart = rect_center_x + 0.32f * rect_w; | |
| 587 | ✗ | auto rect_offset = -static_cast<int>(rect_w * 0.25f * 0.25f); | |
| 588 | ✗ | rect.Min.x += rect_offset; | |
| 589 | ✗ | rect.Max.x += rect_offset; | |
| 590 | ✗ | rect_x += rect_offset; | |
| 591 | ✗ | rect_center_x += rect_offset * 0.5f; | |
| 592 | ✗ | rect_center.x += rect_offset * 0.5f; | |
| 593 | |||
| 594 | ✗ | const auto c = rect_center; | |
| 595 | ✗ | if (style.filled) { | |
| 596 | ✗ | const auto r = 0.5f * rect_w / 2.0f - 0.5f; | |
| 597 | |||
| 598 | ✗ | if (style.fill & 0xFF000000) { | |
| 599 | ✗ | draw_list->AddCircleFilled(c, r, style.fill, 12 + extra_segments); | |
| 600 | } | ||
| 601 | |||
| 602 | ✗ | draw_list->AddCircle(c, r, style.color, 12 + extra_segments, 2.0f * outline_scale); | |
| 603 | } else { | ||
| 604 | ✗ | draw_list->AddCircle(c, 0.5f * rect_w / 2.0f, style.color, 12 + extra_segments); | |
| 605 | } | ||
| 606 | |||
| 607 | ✗ | const auto triangleTip = triangleStart + rect_w * (0.45f - 0.32f); | |
| 608 | |||
| 609 | ✗ | draw_list->AddTriangleFilled(ImVec2(ceilf(triangleTip), rect_y + rect_h * 0.5f), | |
| 610 | ✗ | ImVec2(triangleStart, rect_center_y + 0.15f * rect_h), | |
| 611 | ✗ | ImVec2(triangleStart, rect_center_y - 0.15f * rect_h), | |
| 612 | style.color); | ||
| 613 | ✗ | } | |
| 614 | |||
| 615 | void GraphEditor::draw_square(PinStyle& style, ImVec2 pos, ImVec2 size) { | ||
| 616 | |||
| 617 | ✗ | ImDrawList* draw_list = ImGui::GetWindowDrawList(); | |
| 618 | |||
| 619 | ✗ | auto rect = ImRect(pos, pos + size); | |
| 620 | ✗ | auto rect_x = rect.Min.x; | |
| 621 | ✗ | auto rect_y = rect.Min.y; | |
| 622 | ✗ | auto rect_w = rect.Max.x - rect.Min.x; | |
| 623 | ✗ | auto rect_h = rect.Max.y - rect.Min.y; | |
| 624 | ✗ | auto rect_center_x = (rect.Min.x + rect.Max.x) * 0.5f; | |
| 625 | ✗ | auto rect_center_y = (rect.Min.y + rect.Max.y) * 0.5f; | |
| 626 | ✗ | auto rect_center = ImVec2(rect_center_x, rect_center_y); | |
| 627 | ✗ | const auto outline_scale = rect_w / 24.0f; | |
| 628 | ✗ | const auto extra_segments = static_cast<int>(2 * outline_scale); // for full circle | |
| 629 | |||
| 630 | ✗ | auto triangleStart = rect_center_x + 0.32f * rect_w; | |
| 631 | ✗ | auto rect_offset = -static_cast<int>(rect_w * 0.25f * 0.25f); | |
| 632 | ✗ | rect.Min.x += rect_offset; | |
| 633 | ✗ | rect.Max.x += rect_offset; | |
| 634 | ✗ | rect_x += rect_offset; | |
| 635 | ✗ | rect_center_x += rect_offset * 0.5f; | |
| 636 | ✗ | rect_center.x += rect_offset * 0.5f; | |
| 637 | |||
| 638 | ✗ | if (style.filled) { | |
| 639 | ✗ | const auto r = 0.5f * rect_w / 2.0f; | |
| 640 | ✗ | const auto p0 = rect_center - ImVec2(r, r); | |
| 641 | ✗ | const auto p1 = rect_center + ImVec2(r, r); | |
| 642 | |||
| 643 | ✗ | draw_list->AddRectFilled(p0, p1, style.color, 0, ImDrawFlags_RoundCornersAll); | |
| 644 | } else { | ||
| 645 | ✗ | const auto r = 0.5f * rect_w / 2.0f - 0.5f; | |
| 646 | ✗ | const auto p0 = rect_center - ImVec2(r, r); | |
| 647 | ✗ | const auto p1 = rect_center + ImVec2(r, r); | |
| 648 | |||
| 649 | ✗ | if (style.fill & 0xFF000000) { | |
| 650 | ✗ | draw_list->AddRectFilled(p0, p1, style.fill, 0, ImDrawFlags_RoundCornersAll); | |
| 651 | } | ||
| 652 | |||
| 653 | ✗ | draw_list->AddRect( | |
| 654 | p0, p1, style.color, 0, ImDrawFlags_RoundCornersAll, 2.0f * outline_scale); | ||
| 655 | } | ||
| 656 | ✗ | } | |
| 657 | |||
| 658 | void GraphEditor::draw_grid(PinStyle& style, ImVec2 pos, ImVec2 size) { | ||
| 659 | ✗ | ImDrawList* draw_list = ImGui::GetWindowDrawList(); | |
| 660 | |||
| 661 | ✗ | auto rect = ImRect(pos, pos + size); | |
| 662 | ✗ | auto rect_x = rect.Min.x; | |
| 663 | ✗ | auto rect_y = rect.Min.y; | |
| 664 | ✗ | auto rect_w = rect.Max.x - rect.Min.x; | |
| 665 | ✗ | auto rect_h = rect.Max.y - rect.Min.y; | |
| 666 | ✗ | auto rect_center_x = (rect.Min.x + rect.Max.x) * 0.5f; | |
| 667 | ✗ | auto rect_center_y = (rect.Min.y + rect.Max.y) * 0.5f; | |
| 668 | ✗ | auto rect_center = ImVec2(rect_center_x, rect_center_y); | |
| 669 | ✗ | const auto outline_scale = rect_w / 24.0f; | |
| 670 | ✗ | const auto extra_segments = static_cast<int>(2 * outline_scale); // for full circle | |
| 671 | |||
| 672 | ✗ | auto triangleStart = rect_center_x + 0.32f * rect_w; | |
| 673 | |||
| 674 | ✗ | auto rect_offset = -static_cast<int>(rect_w * 0.25f * 0.25f); | |
| 675 | |||
| 676 | ✗ | rect.Min.x += rect_offset; | |
| 677 | ✗ | rect.Max.x += rect_offset; | |
| 678 | ✗ | rect_x += rect_offset; | |
| 679 | ✗ | rect_center_x += rect_offset * 0.5f; | |
| 680 | ✗ | rect_center.x += rect_offset * 0.5f; | |
| 681 | |||
| 682 | ✗ | const auto r = 0.5f * rect_w / 2.0f; | |
| 683 | ✗ | const auto w = ceilf(r / 3.0f); | |
| 684 | |||
| 685 | ✗ | const auto baseTl = ImVec2(floorf(rect_center_x - w * 2.5f), floorf(rect_center_y - w * 2.5f)); | |
| 686 | ✗ | const auto baseBr = ImVec2(floorf(baseTl.x + w), floorf(baseTl.y + w)); | |
| 687 | |||
| 688 | ✗ | auto tl = baseTl; | |
| 689 | ✗ | auto br = baseBr; | |
| 690 | ✗ | for (int i = 0; i < 3; ++i) { | |
| 691 | ✗ | tl.x = baseTl.x; | |
| 692 | ✗ | br.x = baseBr.x; | |
| 693 | ✗ | draw_list->AddRectFilled(tl, br, style.color); | |
| 694 | ✗ | tl.x += w * 2; | |
| 695 | ✗ | br.x += w * 2; | |
| 696 | |||
| 697 | ✗ | if (i != 1 || style.filled) | |
| 698 | ✗ | draw_list->AddRectFilled(tl, br, style.color); | |
| 699 | |||
| 700 | ✗ | tl.x += w * 2; | |
| 701 | ✗ | br.x += w * 2; | |
| 702 | ✗ | draw_list->AddRectFilled(tl, br, style.color); | |
| 703 | |||
| 704 | ✗ | tl.y += w * 2; | |
| 705 | ✗ | br.y += w * 2; | |
| 706 | } | ||
| 707 | |||
| 708 | // --- | ||
| 709 | ✗ | triangleStart = br.x + w + 1.0f / 24.0f * rect_w; | |
| 710 | ✗ | const auto triangleTip = triangleStart + rect_w * (0.45f - 0.32f); | |
| 711 | ✗ | draw_list->AddTriangleFilled(ImVec2(ceilf(triangleTip), rect_y + rect_h * 0.5f), | |
| 712 | ✗ | ImVec2(triangleStart, rect_center_y + 0.15f * rect_h), | |
| 713 | ✗ | ImVec2(triangleStart, rect_center_y - 0.15f * rect_h), | |
| 714 | style.color); | ||
| 715 | ✗ | } | |
| 716 | |||
| 717 | void GraphEditor::draw_round_square(PinStyle& style, ImVec2 pos, ImVec2 size) { | ||
| 718 | ✗ | ImDrawList* draw_list = ImGui::GetWindowDrawList(); | |
| 719 | |||
| 720 | ✗ | auto rect = ImRect(pos, pos + size); | |
| 721 | ✗ | auto rect_x = rect.Min.x; | |
| 722 | ✗ | auto rect_y = rect.Min.y; | |
| 723 | ✗ | auto rect_w = rect.Max.x - rect.Min.x; | |
| 724 | ✗ | auto rect_h = rect.Max.y - rect.Min.y; | |
| 725 | ✗ | auto rect_center_x = (rect.Min.x + rect.Max.x) * 0.5f; | |
| 726 | ✗ | auto rect_center_y = (rect.Min.y + rect.Max.y) * 0.5f; | |
| 727 | ✗ | auto rect_center = ImVec2(rect_center_x, rect_center_y); | |
| 728 | ✗ | const auto outline_scale = rect_w / 24.0f; | |
| 729 | ✗ | const auto extra_segments = static_cast<int>(2 * outline_scale); // for full circle | |
| 730 | |||
| 731 | ✗ | auto triangleStart = rect_center_x + 0.32f * rect_w; | |
| 732 | ✗ | auto rect_offset = -static_cast<int>(rect_w * 0.25f * 0.25f); | |
| 733 | ✗ | rect.Min.x += rect_offset; | |
| 734 | ✗ | rect.Max.x += rect_offset; | |
| 735 | ✗ | rect_x += rect_offset; | |
| 736 | ✗ | rect_center_x += rect_offset * 0.5f; | |
| 737 | ✗ | rect_center.x += rect_offset * 0.5f; | |
| 738 | |||
| 739 | ✗ | if (style.filled) { | |
| 740 | ✗ | const auto r = 0.5f * rect_w / 2.0f; | |
| 741 | ✗ | const auto cr = r * 0.5f; | |
| 742 | ✗ | const auto p0 = rect_center - ImVec2(r, r); | |
| 743 | ✗ | const auto p1 = rect_center + ImVec2(r, r); | |
| 744 | |||
| 745 | ✗ | draw_list->AddRectFilled(p0, p1, style.color, cr, ImDrawFlags_RoundCornersAll); | |
| 746 | } else { | ||
| 747 | ✗ | const auto r = 0.5f * rect_w / 2.0f - 0.5f; | |
| 748 | ✗ | const auto cr = r * 0.5f; | |
| 749 | ✗ | const auto p0 = rect_center - ImVec2(r, r); | |
| 750 | ✗ | const auto p1 = rect_center + ImVec2(r, r); | |
| 751 | |||
| 752 | ✗ | if (style.fill & 0xFF000000) { | |
| 753 | ✗ | draw_list->AddRectFilled(p0, p1, style.fill, cr, ImDrawFlags_RoundCornersAll); | |
| 754 | } | ||
| 755 | |||
| 756 | ✗ | draw_list->AddRect( | |
| 757 | p0, p1, style.color, cr, ImDrawFlags_RoundCornersAll, 2.0f * outline_scale); | ||
| 758 | } | ||
| 759 | ✗ | } | |
| 760 | void GraphEditor::draw_diamond(PinStyle& style, ImVec2 pos, ImVec2 size) { | ||
| 761 | ✗ | ImDrawList* draw_list = ImGui::GetWindowDrawList(); | |
| 762 | |||
| 763 | ✗ | auto rect = ImRect(pos, pos + size); | |
| 764 | ✗ | auto rect_x = rect.Min.x; | |
| 765 | ✗ | auto rect_y = rect.Min.y; | |
| 766 | ✗ | auto rect_w = rect.Max.x - rect.Min.x; | |
| 767 | ✗ | auto rect_h = rect.Max.y - rect.Min.y; | |
| 768 | ✗ | auto rect_center_x = (rect.Min.x + rect.Max.x) * 0.5f; | |
| 769 | ✗ | auto rect_center_y = (rect.Min.y + rect.Max.y) * 0.5f; | |
| 770 | ✗ | auto rect_center = ImVec2(rect_center_x, rect_center_y); | |
| 771 | ✗ | const auto outline_scale = rect_w / 24.0f; | |
| 772 | ✗ | const auto extra_segments = static_cast<int>(2 * outline_scale); // for full circle | |
| 773 | |||
| 774 | ✗ | auto triangleStart = rect_center_x + 0.32f * rect_w; | |
| 775 | ✗ | auto rect_offset = -static_cast<int>(rect_w * 0.25f * 0.25f); | |
| 776 | ✗ | rect.Min.x += rect_offset; | |
| 777 | ✗ | rect.Max.x += rect_offset; | |
| 778 | ✗ | rect_x += rect_offset; | |
| 779 | ✗ | rect_center_x += rect_offset * 0.5f; | |
| 780 | ✗ | rect_center.x += rect_offset * 0.5f; | |
| 781 | |||
| 782 | ✗ | if (style.filled) { | |
| 783 | ✗ | const auto r = 0.607f * rect_w / 2.0f; | |
| 784 | ✗ | const auto c = rect_center; | |
| 785 | |||
| 786 | ✗ | draw_list->PathLineTo(c + ImVec2(0, -r)); | |
| 787 | ✗ | draw_list->PathLineTo(c + ImVec2(r, 0)); | |
| 788 | ✗ | draw_list->PathLineTo(c + ImVec2(0, r)); | |
| 789 | ✗ | draw_list->PathLineTo(c + ImVec2(-r, 0)); | |
| 790 | ✗ | draw_list->PathFillConvex(style.color); | |
| 791 | } else { | ||
| 792 | ✗ | const auto r = 0.607f * rect_w / 2.0f - 0.5f; | |
| 793 | ✗ | const auto c = rect_center; | |
| 794 | |||
| 795 | ✗ | draw_list->PathLineTo(c + ImVec2(0, -r)); | |
| 796 | ✗ | draw_list->PathLineTo(c + ImVec2(r, 0)); | |
| 797 | ✗ | draw_list->PathLineTo(c + ImVec2(0, r)); | |
| 798 | ✗ | draw_list->PathLineTo(c + ImVec2(-r, 0)); | |
| 799 | |||
| 800 | ✗ | if (style.fill & 0xFF000000) | |
| 801 | ✗ | draw_list->AddConvexPolyFilled( | |
| 802 | ✗ | draw_list->_Path.Data, draw_list->_Path.Size, style.fill); | |
| 803 | |||
| 804 | ✗ | draw_list->PathStroke(style.color, true, 2.0f * outline_scale); | |
| 805 | } | ||
| 806 | ✗ | } | |
| 807 | |||
| 808 | void GraphEditor::draw_triangle(PinStyle& style, ImVec2 pos, ImVec2 size) { | ||
| 809 | ✗ | ImDrawList* draw_list = ImGui::GetWindowDrawList(); | |
| 810 | |||
| 811 | ✗ | auto rect = ImRect(pos, pos + size); | |
| 812 | ✗ | auto rect_x = rect.Min.x; | |
| 813 | ✗ | auto rect_y = rect.Min.y; | |
| 814 | ✗ | auto rect_w = rect.Max.x - rect.Min.x; | |
| 815 | ✗ | auto rect_h = rect.Max.y - rect.Min.y; | |
| 816 | ✗ | auto rect_center_x = (rect.Min.x + rect.Max.x) * 0.5f; | |
| 817 | ✗ | auto rect_center_y = (rect.Min.y + rect.Max.y) * 0.5f; | |
| 818 | ✗ | auto rect_center = ImVec2(rect_center_x, rect_center_y); | |
| 819 | ✗ | auto triangleStart = rect_center_x + 0.32f * rect_w; | |
| 820 | ✗ | const auto triangleTip = triangleStart + rect_w * (0.45f - 0.32f); | |
| 821 | |||
| 822 | ✗ | draw_list->AddTriangleFilled(ImVec2(ceilf(triangleTip), rect_y + rect_h * 0.5f), | |
| 823 | ✗ | ImVec2(triangleStart, rect_center_y + 0.15f * rect_h), | |
| 824 | ✗ | ImVec2(triangleStart, rect_center_y - 0.15f * rect_h), | |
| 825 | style.color); | ||
| 826 | ✗ | } | |
| 827 |