dlxjj/imradv3
0262
1#include "node_standard.h"2#include "node_container.h"3#include "node_extra.h"4#include "stx.h"5#include "cppgen.h"6#include "binding_input.h"7#include "binding_eval.h"8#include "ui_message_box.h"9#include "ui_combo_dlg.h"10#include <misc/cpp/imgui_stdlib.h>11#include <nfd.h>12#include <algorithm>13#include <array>14 15const std::string TMP_LAST_ITEM_VAR = "tmpLastItem";16 17void toggle(std::vector<UINode*>& c, UINode* val)18{19 auto it = stx::find(c, val);20 if (it == c.end())21 c.push_back(val);22 else23 c.erase(it);24}25 26// for compatibility with ImRAD 0.727std::string ParseShortcutOld(const std::string& line)28{29 std::string sh;30 size_t i = -1;31 while (true)32 {33 size_t j1 = line.find("ImGui::IsKeyPressed(ImGuiKey_", i + 1);34 size_t j2 = line.find("ImGuiMod_", i + 1);35 if (j1 == std::string::npos && j2 == std::string::npos)36 break;37 if (j1 < j2) {38 j1 += 29;39 size_t e = line.find_first_of(",)", j1);40 if (e == std::string::npos)41 break;42 sh += "+";43 sh += line.substr(j1, e - j1);44 i = j1;45 }46 else47 {48 j2 += 9;49 sh += "+";50 size_t end = std::find_if(line.begin() + j2, line.end(), [](char c) {51 return !std::isalpha(c);52 }) - line.begin();53 sh += line.substr(j2, end - j2);54 i = j2;55 }56 }57 if (sh.size())58 sh.erase(sh.begin());59 return sh;60}61 62void TreeNodeProp(const char* name, ImFont* font, const std::string& label, std::function<void()> f)63{64 ImVec2 pad = ImGui::GetStyle().FramePadding;65 ImGui::Unindent();66 ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, { 0.0f, pad.y });67 ImGui::PushStyleColor(ImGuiCol_NavCursor, { 0, 0, 0, 0 });68 ImGui::SetNextItemAllowOverlap();69 if (ImGui::TreeNodeEx(name, ImGuiTreeNodeFlags_SpanAllColumns)) {70 ImGui::PopStyleVar();71 ImGui::Indent();72 ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, { pad.x, 0 }); //row packing73 //ImGui::TableNextColumn();74 //ImGui::Spacing();75 f();76 ImGui::PopStyleVar();77 ImGui::TreePop();78 ImGui::Unindent();79 }80 else81 {82 ImGui::PopStyleVar();83 ImGui::TableNextColumn();84 ImGui::SetNextItemWidth(-ImGui::GetFrameHeight());85 ImGui::PushFont(font);86 ImGui::Text("%s", label.c_str());87 ImGui::PopFont();88 }89 ImGui::Indent();90 ImGui::PopStyleColor();91}92 93//1. limits length of lengthy {} expression (like in case it contains ?:)94//2. formats {{, }} into {, }95//3. errors out on incorrect format string96PreparedString PrepareString(std::string_view s)97{98 const int n = 25;99 PreparedString ps;100 ps.error = false;101 ps.pos = ImGui::GetCursorScreenPos();102 ps.pos.y += ImGui::GetCurrentWindow()->DC.CurrLineTextBaseOffset;103 ps.label.reserve(s.size() + 10);104 size_t argFrom = 0;105 for (size_t i = 0; i < s.size(); ++i)106 {107 if (s[i] == '{') {108 ps.label += "{";109 if (i + 1 < s.size() && s[i + 1] == '{')110 ++i;111 else {112 if (argFrom) {113 ps.error = true;114 break;115 }116 argFrom = i + 1;117 }118 }119 else if (s[i] == '}') {120 if (!argFrom) {121 if (i + 1 == s.size() || s[i + 1] != '}') {122 ps.error = true;123 break;124 }125 ps.label += "}";126 ++i;127 }128 else {129 if (i == argFrom) {130 ps.error = true;131 break;132 }133 if (i - argFrom > n) {134 ps.label += s.substr(argFrom, n - 3);135 while (ps.label.back() < 0) //strip incomplete unicode char136 ps.label.pop_back();137 ps.label += "...}";138 }139 else {140 ps.label += s.substr(argFrom, i - argFrom);141 ps.label += "}";142 }143 ps.fmtArgs.push_back({ argFrom - 1, ps.label.size() });144 argFrom = 0;145 }146 }147 else if (!argFrom) {148 ps.label += s[i];149 }150 }151 if (argFrom)152 ps.error = true;153 154 if (ps.error) {155 ps.label = "error";156 ps.fmtArgs.clear();157 }158 return ps;159}160 161ImVec2 IncWrapText(const ImVec2& dpos, const char* s, const char* text_end, float wrap_width, float scale)162{163 ImFont* font = ImGui::GetFont();164 const float line_height = font->FontSize * scale;165 float line_width = dpos.x;166 float text_height = dpos.y;167 const char* word_wrap_eol = NULL;168 const char* real_end = s + strlen(s);169 while (s < text_end)170 {171 // Calculate how far we can render. Requires two passes on the string data but keeps the code simple and not intrusive for what's essentially an uncommon feature.172 if (!word_wrap_eol)173 {174 word_wrap_eol = font->CalcWordWrapPositionA(scale, s, real_end, wrap_width - line_width);175 }176 177 if (s >= word_wrap_eol)178 {179 text_height += line_height;180 line_width = 0.0f;181 word_wrap_eol = NULL;182 //Wrapping skips upcoming blanks183 while (s < text_end && ImCharIsBlankA(*s))184 s++;185 if (*s == '\n')186 s++;187 continue;188 }189 190 // Decode and advance source191 const char* prev_s = s;192 unsigned int c = (unsigned int)*s;193 if (c < 0x80)194 s += 1;195 else196 s += ImTextCharFromUtf8(&c, s, text_end);197 198 if (c < 32)199 {200 if (c == '\n')201 {202 text_height += line_height;203 line_width = 0.0f;204 continue;205 }206 if (c == '\r')207 continue;208 }209 210 const float char_width = scale * ((int)c < font->IndexAdvanceX.Size ? font->IndexAdvanceX.Data[c] : font->FallbackAdvanceX);211 /*if (line_width + char_width >= max_width)212 {213 s = prev_s;214 break;215 }*/216 217 line_width += char_width;218 }219 220 if (s == word_wrap_eol)221 {222 text_height += line_height;223 line_width = 0.0f;224 }225 226 return { line_width, text_height };227}228 229void DrawTextArgs(const PreparedString& ps, UIContext& ctx, const ImVec2& offset, const ImVec2& size, const ImVec2& align)230{231 if (ctx.beingResized)232 return;233 234 ImVec2 pos = ps.pos;235 uint32_t clr = ctx.colors[UIContext::Color::Selected];236 clr = (clr & 0x00ffffff) | 0xb0000000;237 float wrapPos = ImGui::GetCurrentWindow()->DC.TextWrapPos;238 239 if (wrapPos < 0 || size.x || size.y || offset.x || offset.y)240 {241 if (align.x || align.y) {242 ImVec2 textSize = ImGui::CalcTextSize(ps.label.data(), ps.label.data() + ps.label.size());243 ImVec2 sz = ImGui::CalcItemSize(size, textSize.x, textSize.y);244 ImVec2 dp{ (sz.x - textSize.x) * align.x, (sz.y - textSize.y) * align.y };245 pos += dp;246 }247 248 pos += offset;249 size_t i = 0;250 for (const auto& arg : ps.fmtArgs) {251 pos.x += ImGui::CalcTextSize(ps.label.data() + i, ps.label.data() + arg.first).x;252 ImGui::GetWindowDrawList()->AddText(pos, clr, "{");253 //ImVec2 sz = ImGui::CalcTextSize(ps.label.data() + arg.first, ps.label.data() + arg.second);254 //ImGui::GetWindowDrawList()->AddRectFilled(pos, pos + sz, 0x50808080);255 ImVec2 sz = ImGui::CalcTextSize(ps.label.data() + arg.first, ps.label.data() + arg.second - 1);256 pos.x += sz.x;257 ImGui::GetWindowDrawList()->AddText(pos, clr, "}");258 i = arg.second - 1;259 }260 261 if (ps.error)262 ImGui::GetWindowDrawList()->AddText(pos, clr, ps.label.c_str());263 }264 else265 {266 float wrapWidth = ImGui::CalcWrapWidthForPos(ps.pos, wrapPos);267 const char* text = ps.label.data();268 ImVec2 dp{ 0, 0 };269 for (const auto& arg : ps.fmtArgs)270 {271 const char* text_end = ps.label.data() + arg.first;272 dp = IncWrapText(dp, text, text_end, wrapWidth, ctx.zoomFactor);273 ImGui::GetWindowDrawList()->AddText(pos + dp, clr, "{");274 text = text_end;275 text_end = ps.label.data() + arg.second - 1;276 dp = IncWrapText(dp, text, text_end, wrapWidth, ctx.zoomFactor);277 ImGui::GetWindowDrawList()->AddText(pos + dp, clr, "}");278 text = text_end;279 }280 281 if (ps.error)282 ImGui::GetWindowDrawList()->AddText(pos, clr, ps.label.c_str());283 }284}285 286//----------------------------------------------------287 288UINode::child_iterator::iter::iter()289 : children(), idx()290{}291 292UINode::child_iterator::iter::iter(children_type& ch, bool freePos)293 : children(&ch), freePos(freePos), idx()294{295 while (!end() && !valid())296 ++idx;297}298 299UINode::child_iterator::iter&300UINode::child_iterator::iter::operator++ () {301 if (end())302 return *this;303 ++idx;304 while (!end() && !valid())305 ++idx;306 return *this;307}308 309UINode::child_iterator::iter310UINode::child_iterator::iter::operator++ (int) {311 iter it(*this);312 ++(*this);313 return it;314}315 316bool UINode::child_iterator::iter::operator== (const iter& it) const317{318 if (end() != it.end())319 return false;320 if (!end())321 return idx == it.idx;322 return true;323}324 325bool UINode::child_iterator::iter::operator!= (const iter& it) const326{327 return !(*this == it);328}329 330UINode::child_iterator::children_type::value_type&331UINode::child_iterator::iter::operator* ()332{333 static children_type::value_type dummy;334 if (end())335 return dummy;336 return children->at(idx);337}338 339const UINode::child_iterator::children_type::value_type&340UINode::child_iterator::iter::operator* () const341{342 static children_type::value_type dummy;343 if (end())344 return dummy;345 return children->at(idx);346}347 348size_t UINode::child_iterator::iter::index() const349{350 return idx;351}352 353bool UINode::child_iterator::iter::end() const354{355 return !children || idx >= children->size();356}357 358bool UINode::child_iterator::iter::valid() const359{360 if (end())361 return false;362 bool fp = children->at(idx)->hasPos;363 return freePos == fp;364}365 366UINode::child_iterator::child_iterator(children_type& children, bool freePos)367 : children(children), freePos(freePos)368{}369 370UINode::child_iterator::iter371UINode::child_iterator::begin() const372{373 return iter(children, freePos);374}375 376UINode::child_iterator::iter377UINode::child_iterator::end() const378{379 return iter();380}381 382UINode::child_iterator::operator bool() const383{384 return begin() != end();385}386 387//--------------------------------------------------------------------388 389void UINode::CloneChildrenFrom(const UINode& node, UIContext& ctx)390{391 children.resize(node.children.size());392 for (size_t i = 0; i < node.children.size(); ++i)393 children[i] = node.children[i]->Clone(ctx);394}395 396void UINode::DrawInteriorRect(UIContext& ctx)397{398 size_t level = ctx.parents.size() - 1;399 ImDrawList* dl = ImGui::GetWindowDrawList();400 int snapCount = UIContext::Color::COUNT - UIContext::Color::Snap1;401 dl->AddRect(cached_pos, cached_pos + cached_size, ctx.colors[UIContext::Snap1 + level % snapCount], 0, 0, 3);402}403 404void UINode::DrawSnap(UIContext& ctx)405{406 ctx.snapNextColumn = 0;407 ctx.snapSameLine = false;408 ctx.snapUseNextSpacing = false;409 ctx.snapSetNextSameLine = false;410 411 const float MARGIN = 7;412 assert(ctx.parents.back() == this);413 size_t level = ctx.parents.size() - 1;414 int snapOp = Behavior();415 ImVec2 m = ImGui::GetMousePos();416 ImVec2 d1 = m - cached_pos;417 ImVec2 d2 = cached_pos + cached_size - m;418 float mind = std::min({ d1.x, d1.y, d2.x, d2.y });419 420 //snap interior (first child)421 if ((snapOp & (SnapInterior | SnapItemInterior)) &&422 mind >= 3 && //allow snapping sides with zero border423 !stx::count_if(children, [](const auto& ch) { return ch->Behavior() & SnapSides; }))424 {425 ctx.snapParent = this;426 child_iterator it(children, true);427 if (it)428 ctx.snapIndex = it.begin().index();429 else430 ctx.snapIndex = children.size();431 DrawInteriorRect(ctx);432 return;433 }434 435 if (!level || !(snapOp & SnapSides))436 return;437 438 //snap side439 UINode* parent = ctx.parents[ctx.parents.size() - 2];440 const auto& pchildren = parent->children;441 size_t i = stx::find_if(pchildren, [&](const auto& ch) {442 return ch.get() == this;443 }) - pchildren.begin();444 if (i == pchildren.size())445 return;446 UINode* clip = parent;447 if (clip->Behavior() & SnapGrandparentClip)448 clip = ctx.parents[ctx.parents.size() - 3];449 if (m.x < clip->cached_pos.x ||450 m.y < clip->cached_pos.y ||451 m.x > clip->cached_pos.x + clip->cached_size.x ||452 m.y > clip->cached_pos.y + clip->cached_size.y)453 return;454 455 int ncols = parent->ColumnCount(ctx);456 int col = 0;457 if (ncols > 1)458 {459 for (size_t j = 0; j <= i; ++j)460 col = (col + pchildren[j]->nextColumn) % ncols;461 }462 bool lastItem = i + 1 == pchildren.size();463 ImGuiDir snapDir = ImGuiDir_None;464 //allow to snap in space between widgets too (don't check all coordinates)465 //works well with parent clip466 if (mind > MARGIN)467 snapDir = ImGuiDir_None;468 else if (d1.x == mind && d1.y >= 0 && d2.y >= 0)469 snapDir = ImGuiDir_Left;470 else if (d2.x == mind && d1.y >= 0 && d2.y >= 0)471 snapDir = ImGuiDir_Right;472 else if (d1.y == mind && d1.x >= 0 && d2.x >= 0)473 snapDir = ImGuiDir_Up;474 else if (d2.y == mind && d1.x >= 0 && d2.x >= 0)475 snapDir = ImGuiDir_Down;476 else if (lastItem && d2.x < 0 && d2.y < 0)477 snapDir = ImGuiDir_Down;478 479 if (snapDir == ImGuiDir_None)480 {481 if (ImRect(cached_pos, cached_pos + cached_size).Contains(m))482 {483 //end of search with no result (interpreted by TopWindow)484 //children were already snapped so we can safely end search here485 ctx.snapParent = parent;486 ctx.snapIndex = -1;487 }488 return;489 }490 491 ImVec2 p;492 float w = 0, h = 0;493 //snapRight and snapDown will extend the checked area to the next widget494 switch (snapDir)495 {496 case ImGuiDir_Left:497 {498 p = cached_pos;499 h = cached_size.y;500 auto& ch = pchildren[i];501 //check we are not pointing into widget on left502 const auto* lch = i && (pchildren[i - 1]->Behavior() & SnapSides) ? pchildren[i - 1].get() : nullptr;503 if (ncols > 1 && ch->nextColumn) {504 if (m.x < cached_pos.x)505 return;506 }507 if (ch->sameLine && lch) {508 float xm = (p.x + lch->cached_pos.x + lch->cached_size.x) / 2.f;509 //avg marker510 if (ch->spacing <= 1) {511 p.x = xm;512 h = std::max(h, lch->cached_size.y);513 }514 if (m.x < xm)515 return;516 }517 ctx.snapParent = parent;518 ctx.snapIndex = i;519 ctx.snapNextColumn = pchildren[i]->nextColumn;520 ctx.snapSameLine = pchildren[i]->sameLine;521 ctx.snapUseNextSpacing = true;522 ctx.snapSetNextSameLine = true;523 break;524 }525 case ImGuiDir_Right:526 {527 p = cached_pos + ImVec2(cached_size.x, 0);528 h = cached_size.y;529 //check we are not pointing into widget on right530 const Widget* rch = nullptr;531 if (i + 1 < pchildren.size() && !pchildren[i + 1]->nextColumn && pchildren[i + 1]->sameLine)532 rch = pchildren[i + 1].get();533 if (rch) {534 float xm = (p.x + rch->cached_pos.x) / 2.f;535 //avg marker536 if (rch->spacing <= 1) {537 p.x = xm;538 h = std::max(h, rch->cached_size.y);539 }540 if (m.x > xm)541 return;542 }543 if (!rch && ncols > 1)544 {545 for (size_t j = i + 1; j < pchildren.size(); ++j)546 {547 if (pchildren[j]->nextColumn) {548 if ((col + pchildren[j]->nextColumn) % ncols > col)549 rch = pchildren[j].get();550 break;551 }552 }553 if (rch && m.x >= rch->cached_pos.x)554 return;555 }556 const auto* nch = i + 1 < pchildren.size() ? pchildren[i + 1].get() : nullptr;557 ctx.snapParent = parent;558 ctx.snapIndex = i + 1;559 ctx.snapNextColumn = 0;560 ctx.snapSameLine = true;561 ctx.snapUseNextSpacing = false;562 break;563 }564 case ImGuiDir_Up:565 case ImGuiDir_Down:566 {567 bool down = snapDir == ImGuiDir_Down;568 p = cached_pos;569 if (down)570 p.y += cached_size.y;571 float x2 = p.x + cached_size.x;572 //find range of widgets in the same row and column573 size_t i1 = i, i2 = i;574 for (int j = (int)i - 1; j >= 0; --j)575 {576 if (ncols > 1 && pchildren[j + 1]->nextColumn)577 break;578 if (!pchildren[j + 1]->sameLine)579 break;580 const auto& ch = pchildren[j];581 i1 = j;582 p.x = ch->cached_pos.x;583 if (down)584 p.y = std::max(p.y, ch->cached_pos.y + ch->cached_size.y);585 }586 for (size_t j = i + 1; j < pchildren.size(); ++j)587 {588 const auto& ch = pchildren[j];589 if (ncols > 1 && ch->nextColumn)590 break;591 if (!ch->sameLine)592 break;593 i2 = j;594 x2 = ch->cached_pos.x + ch->cached_size.x;595 if (down)596 p.y = std::max(p.y, ch->cached_pos.y + ch->cached_size.y);597 }598 //find a widget from next/prev column-row599 size_t inr = -1, ipr = -1;600 if (ncols >= 2)601 {602 int nc = ncols - col;603 for (size_t j = i + 1; j < pchildren.size(); ++j)604 {605 nc -= pchildren[j]->nextColumn;606 if (nc <= 0) {607 inr = j;608 break;609 }610 }611 nc = ncols;612 for (int j = (int)i; j >= 0; --j)613 {614 if (nc <= 0) {615 ipr = j;616 break;617 }618 nc -= pchildren[j]->nextColumn;619 }620 }621 w = x2 - p.x;622 if (down)623 {624 //check m.y not pointing in next column-row625 const Widget* nch = inr < pchildren.size() ? pchildren[inr].get() : nullptr;626 if (nch)627 {628 if (m.y >= nch->cached_pos.y)629 return;630 }631 //check m.y not pointing in next row632 nch = i2 + 1 < pchildren.size() ? pchildren[i2 + 1].get() : nullptr;633 if (nch && (ncols <= 1 || !nch->nextColumn))634 {635 if (m.y >= (p.y + nch->cached_pos.y) / 2.f)636 return;637 }638 ctx.snapParent = parent;639 ctx.snapIndex = i2 + 1;640 ctx.snapNextColumn = 0;641 ctx.snapSameLine = false;642 ctx.snapUseNextSpacing = false;643 }644 else645 {646 //check m.y is not pointing in previous column-row647 const Widget* pch = ipr < pchildren.size() ? pchildren[ipr].get() : nullptr;648 if (pch)649 {650 if (m.y <= pch->cached_pos.y + pch->cached_size.y)651 return;652 }653 //check m.y not pointing in prev row654 pch = i1 > 0 ? pchildren[i1 - 1].get() : nullptr;655 if (pch && (ncols <= 1 || !pchildren[i1]->nextColumn))656 {657 if (m.y <= pch->cached_pos.y + pch->cached_size.y)658 return;659 }660 ctx.snapParent = parent;661 ctx.snapIndex = i1;662 ctx.snapSameLine = false;663 ctx.snapNextColumn = pchildren[i1]->nextColumn;664 ctx.snapUseNextSpacing = true;665 }666 break;667 }668 default:669 return;670 }671 672 ImDrawList* dl = ImGui::GetWindowDrawList();673 dl->AddLine(p, p + ImVec2(w, h), ctx.colors[UIContext::Snap1 + (level - 1)], 3);674}675 676std::optional<std::pair<UINode*, int>>677UINode::FindChild(const UINode* ch)678{679 if (ch == this)680 return std::pair{ nullptr, 0 };681 for (size_t i = 0; i < children.size(); ++i) {682 const auto& child = children[i];683 if (child.get() == ch)684 return std::pair{ this, (int)i };685 auto tmp = child->FindChild(ch);686 if (tmp)687 return tmp;688 }689 return {};690}691 692std::vector<UINode*>693UINode::FindInRect(const ImRect& r)694{695 std::vector<UINode*> sel;696 697 if (cached_size.x && cached_size.y && //skip contextMenu698 cached_pos.x > r.Min.x &&699 cached_pos.y > r.Min.y &&700 cached_pos.x + cached_size.x < r.Max.x &&701 cached_pos.y + cached_size.y < r.Max.y)702 sel.push_back(this);703 704 for (const auto& child : children) {705 auto chsel = child->FindInRect(r);706 sel.insert(sel.end(), chsel.begin(), chsel.end());707 }708 return sel;709}710 711std::vector<UINode*>712UINode::GetAllChildren()713{714 std::vector<UINode*> chs;715 chs.reserve(children.size() * 2);716 chs.push_back(this);717 for (const auto& child : children) {718 auto vec = child->GetAllChildren();719 chs.insert(chs.end(), vec.begin(), vec.end());720 }721 return chs;722}723 724//this must return exact class id725std::string UINode::GetTypeName()726{727 std::string name = typeid(*this).name();728 //erase "struct"729 auto i = name.rfind(' ');730 if (i != std::string::npos)731 name.erase(0, i + 1);732 //erase encoded name length733 auto it = stx::find_if(name, [](char c) { return isalpha(c); });734 if (it != name.end())735 name.erase(0, it - name.begin());736 return name;737}738 739int GetTotalIndex(UINode* parent, UINode* child)740{741 std::string tname = child->GetTypeName();742 int idx = 0;743 for (const auto& ch : parent->children) {744 if (ch.get() == child)745 return idx;746 idx += GetTotalIndex(ch.get(), child);747 if (ch->GetTypeName() == tname)748 ++idx;749 }750 return idx;751}752 753void UINode::PushError(UIContext& ctx, const std::string& err)754{755 std::string name = GetTypeName();756 if (this != ctx.root) {757 int idx = GetTotalIndex(ctx.parents[0], this);758 name = name + " #" + std::to_string(idx+1);759 }760 ctx.errors.push_back(name + " : " + err);761}762 763std::string UINode::GetParentIndexes(UIContext& ctx)764{765 std::string id;766 UINode* node = ctx.parents.back();767 for (auto it = ++ctx.parents.rbegin(); it != ctx.parents.rend(); ++it) {768 size_t i = stx::find_if((*it)->children, [=](const auto& ch) {769 return ch.get() == node;770 }) - (*it)->children.begin();771 id = std::to_string(i) + id;772 node = *it;773 }774 return id;775}776 777void UINode::ResetLayout()778{779 hbox.clear();780 vbox.clear();781 for (auto& ch : children)782 ch->ResetLayout();783}784 785std::vector<std::string> UINode::UsedFieldVars()786{787 std::vector<std::string> used;788 auto props = Properties();789 for (auto& p : props) {790 if (!p.property)791 continue;792 auto us = p.property->used_variables();793 used.insert(used.end(), us.begin(), us.end());794 }795 for (auto& child : children) {796 auto us = child->UsedFieldVars();797 used.insert(used.end(), us.begin(), us.end());798 }799 stx::sort(used);800 used.erase(stx::unique(used), used.end());801 return used;802}803 804void UINode::RenameFieldVars(const std::string& oldn, const std::string& newn)805{806 for (int i = 0; i < 2; ++i)807 {808 auto props = i ? Events() : Properties();809 for (auto& p : props) {810 if (!p.property)811 continue;812 p.property->rename_variable(oldn, newn);813 }814 }815 for (auto& child : children)816 child->RenameFieldVars(oldn, newn);817}818 819//----------------------------------------------------820 821std::unique_ptr<Widget>822Widget::Create(const std::string& name, UIContext& ctx)823{824 if (name == "Text")825 return std::make_unique<Text>(ctx);826 else if (name == "Selectable")827 return std::make_unique<Selectable>(ctx);828 else if (name == "Button")829 return std::make_unique<Button>(ctx);830 else if (name == "CheckBox")831 return std::make_unique<CheckBox>(ctx);832 else if (name == "RadioButton")833 return std::make_unique<RadioButton>(ctx);834 else if (name == "Input")835 return std::make_unique<Input>(ctx);836 else if (name == "Combo")837 return std::make_unique<Combo>(ctx);838 else if (name == "Slider")839 return std::make_unique<Slider>(ctx);840 else if (name == "ProgressBar")841 return std::make_unique<ProgressBar>(ctx);842 else if (name == "ColorEdit")843 return std::make_unique<ColorEdit>(ctx);844 else if (name == "Image")845 return std::make_unique<Image>(ctx);846 else if (name == "Spacer")847 return std::make_unique<Spacer>(ctx);848 else if (name == "Separator")849 return std::make_unique<Separator>(ctx);850 else if (name == "CustomWidget")851 return std::make_unique<CustomWidget>(ctx);852 else if (name == "Table")853 return std::make_unique<Table>(ctx);854 else if (name == "Child")855 return std::make_unique<Child>(ctx);856 else if (name == "CollapsingHeader")857 return std::make_unique<CollapsingHeader>(ctx);858 else if (name == "TabBar")859 return std::make_unique<TabBar>(ctx);860 else if (name == "TabItem")861 return std::make_unique<TabItem>(ctx);862 else if (name == "TreeNode")863 return std::make_unique<TreeNode>(ctx);864 else if (name == "MenuBar")865 return std::make_unique<MenuBar>(ctx);866 else if (name == "ContextMenu")867 return std::make_unique<ContextMenu>(ctx);868 else if (name == "MenuIt")869 return std::make_unique<MenuIt>(ctx);870 else if (name == "Splitter")871 return std::make_unique<Splitter>(ctx);872 else if (name == "DockSpace")873 return std::make_unique<DockSpace>(ctx);874 else if (name == "DockNode")875 return std::make_unique<DockNode>(ctx);876 else877 return {};878}879 880Widget::Widget()881{882 hasPos.add("None", ImRad::AlignNone);883 hasPos.add("TopLeft", ImRad::AlignLeft | ImRad::AlignTop);884 hasPos.add("TopCenter", ImRad::AlignHCenter | ImRad::AlignTop);885 hasPos.add("TopRight", ImRad::AlignRight | ImRad::AlignTop);886 hasPos.add("LeftCenter", ImRad::AlignLeft | ImRad::AlignVCenter);887 hasPos.add("Center", ImRad::AlignHCenter | ImRad::AlignVCenter);888 hasPos.add("RightCenter", ImRad::AlignRight | ImRad::AlignVCenter);889 hasPos.add("BottomLeft", ImRad::AlignLeft | ImRad::AlignBottom);890 hasPos.add("BottomCenter", ImRad::AlignHCenter | ImRad::AlignBottom);891 hasPos.add("BottomRight", ImRad::AlignRight | ImRad::AlignBottom);892 893 cursor.add$(ImGuiMouseCursor_None);894 cursor.add$(ImGuiMouseCursor_Arrow);895 cursor.add$(ImGuiMouseCursor_TextInput);896 cursor.add$(ImGuiMouseCursor_ResizeAll);897 cursor.add$(ImGuiMouseCursor_ResizeNS);898 cursor.add$(ImGuiMouseCursor_ResizeEW);899 cursor.add$(ImGuiMouseCursor_ResizeNESW);900 cursor.add$(ImGuiMouseCursor_ResizeNWSE);901 cursor.add$(ImGuiMouseCursor_Hand);902 cursor.add$(ImGuiMouseCursor_NotAllowed);903}904 905int Widget::Behavior()906{907 return hasPos ? 0 : SnapSides;908}909 910//detects if widget is leftmost/topmost in its row911//colId, rowId - use as index to parent.hbox/vbox912//any usage of stretched dimension triggers HLayout/VLayout for that row/column913Widget::Layout Widget::GetLayout(UINode* parent)914{915 Layout l;916 l.colId = l.rowId = -1;917 l.index = -1;918 919 if (hasPos || !(Behavior() & SnapSides))920 {921 l.flags |= Layout::Topmost | Layout::Leftmost;922 return l;923 }924 925 bool firstWidget = true;926 bool leftmost = true;927 bool topmost = true;928 bool bottommost = false;929 bool hlay = false;930 bool vlay = false;931 int colId = 0;932 int rowId = 0;933 for (const auto& child : parent->children)934 {935 if (child->hasPos || !(child->Behavior() & SnapSides)) //ignore MenuBar etc.936 continue;937 938 if ((!child->sameLine || child->nextColumn) && !firstWidget)939 {940 if (colId == l.colId)941 l.flags |= vlay * Layout::VLayout;942 if (rowId == l.rowId)943 l.flags |= hlay * Layout::HLayout;944 ++rowId;945 topmost = child->nextColumn;946 leftmost = true;947 bottommost = false;948 hlay = false;949 if (child->nextColumn)950 {951 vlay = false;952 colId += child->nextColumn;953 }954 }955 else if (child->sameLine && !child->nextColumn) {956 leftmost = false;957 }958 959 if (child->size_y.stretched())960 vlay = true;961 if (child->size_x.stretched())962 hlay = true;963 964 if (child.get() == this) {965 l.index = &child - parent->children.data();966 l.colId = colId;967 l.rowId = rowId;968 l.flags |= (leftmost * Layout::Leftmost) | (topmost * Layout::Topmost);969 bottommost = true;970 }971 972 firstWidget = false;973 }974 if (bottommost)975 l.flags |= Layout::Bottommost;976 if (colId == l.colId)977 l.flags |= vlay * Layout::VLayout;978 if (rowId == l.rowId)979 l.flags |= hlay * Layout::HLayout;980 981 return l;982}983 984void Widget::Draw(UIContext& ctx)985{986 UINode* parent = ctx.parents.back();987 Layout l = GetLayout(parent);988 const int defSpacing = (l.flags & Layout::Topmost) ? 0 : 1;989 ctx.stretchSize = { 0, 0 };990 991 if (!hasPos && nextColumn) {992 bool inTable = dynamic_cast<Table*>(parent);993 if (inTable)994 ImRad::TableNextColumn(nextColumn);995 else996 ImRad::NextColumn(nextColumn);997 }998 999 if (hasPos)1000 {1001 ImRect r = parent->Behavior() & SnapItemInterior ?1002 ImRect{ parent->cached_pos, parent->cached_pos + parent->cached_size } :1003 ImRad::GetParentInnerRect();1004 ImVec2 pos{ pos_x.eval_px(ImGuiAxis_X, ctx), pos_y.eval_px(ImGuiAxis_Y, ctx) };1005 if (hasPos & ImRad::AlignLeft)1006 pos.x += r.Min.x;1007 else if (hasPos & ImRad::AlignRight)1008 pos.x += r.Max.x;1009 else if (hasPos & ImRad::AlignHCenter)1010 pos.x += r.GetCenter().x;1011 if (hasPos & ImRad::AlignTop)1012 pos.y += r.Min.y;1013 else if (hasPos & ImRad::AlignBottom)1014 pos.y += r.Max.y;1015 else if (hasPos & ImRad::AlignVCenter)1016 pos.y += r.GetCenter().y;1017 ImGui::SetCursorScreenPos(pos);1018 }1019 else if (l.flags & (Layout::HLayout | Layout::VLayout))1020 {1021 ImRad::VBox* vbox = nullptr;1022 ImRad::HBox *hbox = nullptr;1023 1024 if (l.flags & Layout::VLayout)1025 {1026 if (l.colId >= parent->vbox.size())1027 parent->vbox.resize(l.colId + 1);1028 vbox = &parent->vbox[l.colId];1029 if ((l.flags & Layout::Topmost) && (l.flags & Layout::Leftmost))1030 vbox->BeginLayout();1031 /*if (l.flags & Layout::Leftmost)1032 ImGui::SetCursorPosY(vbox);*/1033 }1034 if ((l.flags & Layout::Leftmost) && (spacing - defSpacing))1035 {1036 ImRad::Spacing(spacing - defSpacing);1037 }1038 if (l.flags & Layout::HLayout)1039 {1040 if (l.rowId >= parent->hbox.size())1041 parent->hbox.resize(l.rowId + 1);1042 hbox = &parent->hbox[l.rowId];1043 if (l.flags & Layout::Leftmost)1044 hbox->BeginLayout();1045 //ImGui::SetCursorPosX(hbox); //currently not needed but may be useful if we upgrade layouts1046 }1047 if (!(l.flags & Layout::Leftmost))1048 {1049 //we need to provide correct spacing and don't use SetCursorX1050 //becausein that case after hbox.Reset() hbox.GetPos() will return CursorPos with1051 //a wrong spacing and it will be used in autosized window contentSize1052 ImGui::SameLine(0, spacing * ImGui::GetStyle().ItemSpacing.x);1053 }1054 //after SameLine was determined1055 if (vbox)1056 ctx.stretchSize.y = vbox->GetSize(); // !(l.flags & Layout::Leftmost));1057 if (hbox)1058 ctx.stretchSize.x = hbox->GetSize();1059 }1060 else1061 {1062 if (sameLine) {1063 ImGui::SameLine(0, spacing * ImGui::GetStyle().ItemSpacing.x);1064 }1065 else {1066 ImRad::Spacing(spacing - defSpacing);1067 }1068 if (indent)1069 ImGui::Indent(indent * ImGui::GetStyle().IndentSpacing / 2);1070 }1071 1072 ImGui::PushID(this);1073 ctx.parents.push_back(this);1074 auto lastHovered = ctx.hovered;1075 auto p1 = ImGui::GetCursorScreenPos();1076 1077 if (style_font.has_value())1078 ImGui::PushFont(ImRad::GetFontByName(style_font.eval(ctx)));1079 if (!style_text.empty())1080 ImGui::PushStyleColor(ImGuiCol_Text, style_text.eval(ImGuiCol_Text, ctx));1081 if (!style_border.empty())1082 ImGui::PushStyleColor(ImGuiCol_Border, style_border.eval(ImGuiCol_Border, ctx));1083 if (!style_frameBg.empty())1084 ImGui::PushStyleColor(ImGuiCol_FrameBg, style_frameBg.eval(ImGuiCol_FrameBg, ctx));1085 if (!style_button.empty())1086 ImGui::PushStyleColor(ImGuiCol_Button, style_button.eval(ImGuiCol_Button, ctx));1087 if (!style_buttonHovered.empty())1088 ImGui::PushStyleColor(ImGuiCol_ButtonHovered, style_buttonHovered.eval(ImGuiCol_ButtonHovered, ctx));1089 if (!style_buttonActive.empty())1090 ImGui::PushStyleColor(ImGuiCol_ButtonActive, style_buttonActive.eval(ImGuiCol_ButtonActive, ctx));1091 if (!style_frameBorderSize.empty())1092 ImGui::PushStyleVar(ImGuiStyleVar_FrameBorderSize, style_frameBorderSize);1093 if (!style_frameRounding.empty())1094 ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, style_frameRounding);1095 if (!style_framePadding.empty())1096 ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, style_framePadding);1097 1098 ImGui::BeginDisabled((!disabled.empty() && disabled.eval(ctx)) || (!visible.empty() && !visible.eval(ctx)));1099 ImDrawList* drawList = DoDraw(ctx);1100 ImGui::EndDisabled();1101 CalcSizeEx(p1, ctx);1102 1103 if (!style_text.empty())1104 ImGui::PopStyleColor();1105 if (!style_border.empty())1106 ImGui::PopStyleColor();1107 if (!style_frameBg.empty())1108 ImGui::PopStyleColor();1109 if (!style_button.empty())1110 ImGui::PopStyleColor();1111 if (!style_buttonHovered.empty())1112 ImGui::PopStyleColor();1113 if (!style_buttonActive.empty())1114 ImGui::PopStyleColor();1115 if (!style_frameBorderSize.empty())1116 ImGui::PopStyleVar();1117 if (!style_frameRounding.empty())1118 ImGui::PopStyleVar();1119 if (!style_framePadding.empty())1120 ImGui::PopStyleVar();1121 if (style_font.has_value())1122 ImGui::PopFont();1123 1124 if (!hasPos)1125 HashCombineData(ctx.layoutHash, ImGui::GetItemID());1126 if (l.flags & Layout::VLayout)1127 {1128 auto& vbox = parent->vbox[l.colId];1129 float sizeY = ImRad::VBox::ItemSize;1130 if (Behavior() & HasSizeY) {1131 sizeY = size_y.stretched() ? (float)size_y.value() :1132 size_y.zero() ? ImRad::VBox::ItemSize :1133 size_y.eval_px(ImGuiAxis_Y, ctx);1134 HashCombineData(ctx.layoutHash, sizeY);1135 }1136 if (size_y.stretched()) {1137 if (l.flags & Layout::Leftmost)1138 vbox.AddSize(spacing, ImRad::VBox::Stretch(sizeY));1139 else1140 vbox.UpdateSize(0, ImRad::VBox::Stretch(sizeY));1141 }1142 else {1143 if (l.flags & Layout::Leftmost)1144 vbox.AddSize(spacing, sizeY);1145 else1146 vbox.UpdateSize(0, sizeY);1147 }1148 }1149 if (l.flags & Layout::HLayout)1150 {1151 auto& hbox = parent->hbox[l.rowId];1152 float sizeX = ImRad::HBox::ItemSize;1153 if (Behavior() & HasSizeX) {1154 sizeX = size_x.stretched() ? (float)size_x.value() :1155 size_x.zero() ? ImRad::HBox::ItemSize :1156 size_x.eval_px(ImGuiAxis_X, ctx);1157 HashCombineData(ctx.layoutHash, sizeX);1158 }1159 int sp = (l.flags & Layout::Leftmost) ? 0 : (int)spacing;1160 if (size_x.stretched())1161 hbox.AddSize(sp, ImRad::HBox::Stretch(sizeX));1162 else1163 hbox.AddSize(sp, sizeX);1164 }1165 1166 //doesn't work for open CollapsingHeader etc:1167 //bool hovered1 = ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled);1168 bool hovered = ImGui::IsMouseHoveringRect(cached_pos, cached_pos + cached_size) &&1169 ImGui::IsWindowHovered(ImGuiHoveredFlags_ChildWindows);1170 if ((ctx.mode == UIContext::NormalSelection || ctx.mode == UIContext::SnapInsert) &&1171 hovered && !ImGui::GetTopMostAndVisiblePopupModal())1172 {1173 //prevent changing cursor to e.g. TextInput1174 //but don't override child.Draw SetMouseCursor1175 if (ctx.hovered == lastHovered)1176 ImGui::SetMouseCursor(ImGuiMouseCursor_Arrow);1177 }1178 if (ctx.mode == UIContext::NormalSelection &&1179 hovered && !ImGui::GetTopMostAndVisiblePopupModal())1180 {1181 if (ImGui::IsMouseReleased(ImGuiMouseButton_Left)) //this works even for non-items like TabControl etc.1182 {1183 if (ImGui::IsKeyDown(ImGuiKey_LeftCtrl) || ImGui::IsKeyDown(ImGuiKey_RightCtrl))1184 {1185 if (!stx::count(ctx.selected, ctx.root))1186 toggle(ctx.selected, this);1187 }1188 else1189 ctx.selected = { this };1190 ImGui::GetIO().MouseReleased[ImGuiMouseButton_Left] = false; //eat event so parent won't get selected1191 }1192 else if (ImGui::IsMouseReleased(ImGuiMouseButton_Right))1193 {1194 if (!stx::count(ctx.selected, this))1195 ctx.selected = { this };1196 ImGui::GetIO().MouseReleased[ImGuiMouseButton_Left] = false; //eat event so parent won't get selected1197 }1198 }1199 bool allowed = !ImGui::GetTopMostAndVisiblePopupModal() &&1200 (ctx.activePopups.empty() || stx::count(ctx.activePopups, ImGui::GetCurrentWindow()));