feat: update to 068e90b418b7da440a15db6495f2a37239d30bff
This commit is contained in:
21
libs/node_editor/LICENSE
Normal file
21
libs/node_editor/LICENSE
Normal 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.
|
||||
890
libs/node_editor/crude_json.cpp
Normal file
890
libs/node_editor/crude_json.cpp
Normal file
@@ -0,0 +1,890 @@
|
||||
// Crude implementation of JSON value object and parser.
|
||||
//
|
||||
// VERSION 0.1
|
||||
//
|
||||
// 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>
|
||||
# if CRUDE_JSON_IO
|
||||
# include <stdio.h>
|
||||
# include <memory>
|
||||
# endif
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
size_t value::erase(const string& key)
|
||||
{
|
||||
if (!is_object())
|
||||
return 0;
|
||||
|
||||
auto& o = *object_ptr(m_Storage);
|
||||
auto it = o.find(key);
|
||||
|
||||
if (it == o.end())
|
||||
return 0;
|
||||
|
||||
o.erase(it);
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
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 = static_cast<int>(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(static_cast<char>(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 (v != 0 && !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;
|
||||
}
|
||||
|
||||
# if CRUDE_JSON_IO
|
||||
std::pair<value, bool> value::load(const string& path)
|
||||
{
|
||||
// Modern C++, so beautiful...
|
||||
std::unique_ptr<FILE, void(*)(FILE*)> file{nullptr, [](FILE* file) { if (file) fclose(file); }};
|
||||
# if defined(_MSC_VER) || (defined(__STDC_LIB_EXT1__) && __STDC_WANT_LIB_EXT1__)
|
||||
FILE* handle = nullptr;
|
||||
if (fopen_s(&handle, path.c_str(), "rb") != 0)
|
||||
return {value{}, false};
|
||||
file.reset(handle);
|
||||
# else
|
||||
file.reset(fopen(path.c_str(), "rb"));
|
||||
# endif
|
||||
|
||||
if (!file)
|
||||
return {value{}, false};
|
||||
|
||||
fseek(file.get(), 0, SEEK_END);
|
||||
auto size = static_cast<size_t>(ftell(file.get()));
|
||||
fseek(file.get(), 0, SEEK_SET);
|
||||
|
||||
string data;
|
||||
data.resize(size);
|
||||
if (fread(const_cast<char*>(data.data()), size, 1, file.get()) != 1)
|
||||
return {value{}, false};
|
||||
|
||||
return {parse(data), true};
|
||||
}
|
||||
|
||||
bool value::save(const string& path, const int indent, const char indent_char) const
|
||||
{
|
||||
// Modern C++, so beautiful...
|
||||
std::unique_ptr<FILE, void(*)(FILE*)> file{nullptr, [](FILE* file) { if (file) fclose(file); }};
|
||||
# if defined(_MSC_VER) || (defined(__STDC_LIB_EXT1__) && __STDC_WANT_LIB_EXT1__)
|
||||
FILE* handle = nullptr;
|
||||
if (fopen_s(&handle, path.c_str(), "wb") != 0)
|
||||
return false;
|
||||
file.reset(handle);
|
||||
# else
|
||||
file.reset(fopen(path.c_str(), "wb"));
|
||||
# endif
|
||||
|
||||
if (!file)
|
||||
return false;
|
||||
|
||||
auto data = dump(indent, indent_char);
|
||||
|
||||
if (fwrite(data.data(), data.size(), 1, file.get()) != 1)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
# endif
|
||||
|
||||
} // namespace crude_json
|
||||
250
libs/node_editor/crude_json.h
Normal file
250
libs/node_editor/crude_json.h
Normal file
@@ -0,0 +1,250 @@
|
||||
// Crude implementation of JSON value object and parser.
|
||||
//
|
||||
// VERSION 0.1
|
||||
//
|
||||
// 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
|
||||
|
||||
# ifndef CRUDE_JSON_IO
|
||||
# define CRUDE_JSON_IO 1
|
||||
# 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);
|
||||
|
||||
size_t erase(const string& key);
|
||||
|
||||
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();
|
||||
|
||||
template <typename T> const T* get_ptr() const;
|
||||
template <typename T> T* get_ptr();
|
||||
|
||||
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);
|
||||
|
||||
# if CRUDE_JSON_IO
|
||||
static std::pair<value, bool> load(const string& path);
|
||||
bool save(const string& path, const int indent = -1, const char indent_char = ' ') const;
|
||||
# endif
|
||||
|
||||
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) { (void)storage; 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); }
|
||||
|
||||
template <> inline const object* value::get_ptr<object>() const { if (m_Type == type_t::object) return object_ptr(m_Storage); else return nullptr; }
|
||||
template <> inline const array* value::get_ptr<array>() const { if (m_Type == type_t::array) return array_ptr(m_Storage); else return nullptr; }
|
||||
template <> inline const string* value::get_ptr<string>() const { if (m_Type == type_t::string) return string_ptr(m_Storage); else return nullptr; }
|
||||
template <> inline const boolean* value::get_ptr<boolean>() const { if (m_Type == type_t::boolean) return boolean_ptr(m_Storage); else return nullptr; }
|
||||
template <> inline const number* value::get_ptr<number>() const { if (m_Type == type_t::number) return number_ptr(m_Storage); else return nullptr; }
|
||||
|
||||
template <> inline object* value::get_ptr<object>() { if (m_Type == type_t::object) return object_ptr(m_Storage); else return nullptr; }
|
||||
template <> inline array* value::get_ptr<array>() { if (m_Type == type_t::array) return array_ptr(m_Storage); else return nullptr; }
|
||||
template <> inline string* value::get_ptr<string>() { if (m_Type == type_t::string) return string_ptr(m_Storage); else return nullptr; }
|
||||
template <> inline boolean* value::get_ptr<boolean>() { if (m_Type == type_t::boolean) return boolean_ptr(m_Storage); else return nullptr; }
|
||||
template <> inline number* value::get_ptr<number>() { if (m_Type == type_t::number) return number_ptr(m_Storage); else return nullptr; }
|
||||
|
||||
} // namespace crude_json
|
||||
|
||||
# endif // __CRUDE_JSON_H__
|
||||
144
libs/node_editor/imgui_bezier_math.h
Normal file
144
libs/node_editor/imgui_bezier_math.h
Normal file
@@ -0,0 +1,144 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// VERSION 0.1
|
||||
//
|
||||
// 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__
|
||||
675
libs/node_editor/imgui_bezier_math.inl
Normal file
675
libs/node_editor/imgui_bezier_math.inl
Normal file
@@ -0,0 +1,675 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// VERSION 0.1
|
||||
//
|
||||
// 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)
|
||||
{
|
||||
IM_UNUSED(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(a, i) == 0.0f)
|
||||
continue;
|
||||
|
||||
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 total_length = ImCubicBezierLength(p0, p1, p2, p3);
|
||||
const auto point_count = static_cast<int>(total_length / step) + (overshoot ? 2 : 1);
|
||||
const auto t_min = 0.0f;
|
||||
const auto t_max = step * point_count / total_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 = total_length;
|
||||
|
||||
while (true)
|
||||
{
|
||||
auto cacheIt = cache.find(t);
|
||||
if (cacheIt == cache.end())
|
||||
{
|
||||
const auto front = ImCubicBezierSplit(p0, p1, p2, p3, t).Left;
|
||||
const auto split_length = ImCubicBezierLength(front);
|
||||
|
||||
cacheIt = cache.emplace(t, split_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__
|
||||
574
libs/node_editor/imgui_canvas.cpp
Normal file
574
libs/node_editor/imgui_canvas.cpp
Normal file
@@ -0,0 +1,574 @@
|
||||
# ifndef IMGUI_DEFINE_MATH_OPERATORS
|
||||
# define IMGUI_DEFINE_MATH_OPERATORS
|
||||
# endif
|
||||
# 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))); \
|
||||
}
|
||||
|
||||
// Special sentinel value. This needs to be unique, so allow it to be overridden in the user's ImGui config
|
||||
# ifndef ImDrawCallback_ImCanvas
|
||||
# define ImDrawCallback_ImCanvas (ImDrawCallback)(-2)
|
||||
# endif
|
||||
|
||||
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;
|
||||
}
|
||||
};
|
||||
|
||||
DECLARE_HAS_MEMBER(HasVtxCurrentOffset, _VtxCurrentOffset);
|
||||
|
||||
struct VtxCurrentOffsetRef
|
||||
{
|
||||
// Overload is present when ImDrawList does have _FringeScale member variable.
|
||||
template <typename T>
|
||||
static unsigned int& Get(typename std::enable_if<HasVtxCurrentOffset<T>::value, T>::type* drawList)
|
||||
{
|
||||
return drawList->_VtxCurrentOffset;
|
||||
}
|
||||
|
||||
// Overload is present when ImDrawList does not have _FringeScale member variable.
|
||||
template <typename T>
|
||||
static unsigned int& Get(typename std::enable_if<!HasVtxCurrentOffset<T>::value, T>::type* drawList)
|
||||
{
|
||||
return drawList->_CmdHeader.VtxOffset;
|
||||
}
|
||||
};
|
||||
|
||||
} // 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 unsigned int& ImVtxOffsetRef(ImDrawList* drawList)
|
||||
{
|
||||
using namespace ImCanvasDetails;
|
||||
return VtxCurrentOffsetRef::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_VERSION_NUM > 18415
|
||||
if (ImGui::IsClippedEx(m_WidgetRect, id))
|
||||
return false;
|
||||
# else
|
||||
if (ImGui::IsClippedEx(m_WidgetRect, id, false))
|
||||
return false;
|
||||
# endif
|
||||
|
||||
// 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();
|
||||
SaveViewportState();
|
||||
|
||||
// Record cursor max to prevent scrollbars from appearing.
|
||||
m_WindowCursorMaxBackup = ImGui::GetCurrentWindow()->DC.CursorMaxPos;
|
||||
|
||||
EnterLocalSpace();
|
||||
|
||||
# if IMGUI_VERSION_NUM >= 18967
|
||||
ImGui::SetNextItemAllowOverlap();
|
||||
# endif
|
||||
|
||||
// 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();
|
||||
|
||||
ImGui::GetCurrentWindow()->DC.CursorMaxPos = m_WindowCursorMaxBackup;
|
||||
|
||||
# if IMGUI_VERSION_NUM < 18967
|
||||
ImGui::SetItemAllowOverlap();
|
||||
# endif
|
||||
|
||||
// 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];
|
||||
}
|
||||
|
||||
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];
|
||||
}
|
||||
|
||||
void ImGuiEx::Canvas::SaveViewportState()
|
||||
{
|
||||
# if defined(IMGUI_HAS_VIEWPORT)
|
||||
auto window = ImGui::GetCurrentWindow();
|
||||
auto viewport = ImGui::GetWindowViewport();
|
||||
|
||||
m_WindowPosBackup = window->Pos;
|
||||
m_ViewportPosBackup = viewport->Pos;
|
||||
m_ViewportSizeBackup = viewport->Size;
|
||||
# if IMGUI_VERSION_NUM > 18002
|
||||
m_ViewportWorkPosBackup = viewport->WorkPos;
|
||||
m_ViewportWorkSizeBackup = viewport->WorkSize;
|
||||
# else
|
||||
m_ViewportWorkOffsetMinBackup = viewport->WorkOffsetMin;
|
||||
m_ViewportWorkOffsetMaxBackup = viewport->WorkOffsetMax;
|
||||
# endif
|
||||
# endif
|
||||
}
|
||||
|
||||
void ImGuiEx::Canvas::RestoreViewportState()
|
||||
{
|
||||
# if defined(IMGUI_HAS_VIEWPORT)
|
||||
auto window = ImGui::GetCurrentWindow();
|
||||
auto viewport = ImGui::GetWindowViewport();
|
||||
|
||||
window->Pos = m_WindowPosBackup;
|
||||
viewport->Pos = m_ViewportPosBackup;
|
||||
viewport->Size = m_ViewportSizeBackup;
|
||||
# if IMGUI_VERSION_NUM > 18002
|
||||
viewport->WorkPos = m_ViewportWorkPosBackup;
|
||||
viewport->WorkSize = m_ViewportWorkSizeBackup;
|
||||
# else
|
||||
viewport->WorkOffsetMin = m_ViewportWorkOffsetMinBackup;
|
||||
viewport->WorkOffsetMax = m_ViewportWorkOffsetMaxBackup;
|
||||
# endif
|
||||
# endif
|
||||
}
|
||||
|
||||
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();
|
||||
|
||||
# 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, 0);
|
||||
m_CurrentRange->BeginVertexIndex = m_DrawList->_VtxCurrentIdx + ImVtxOffsetRef(m_DrawList);
|
||||
# endif
|
||||
m_DrawListCommadBufferSize = ImMax(m_DrawList->CmdBuffer.Size, 0);
|
||||
m_DrawListStartVertexIndex = m_DrawList->_VtxCurrentIdx + ImVtxOffsetRef(m_DrawList);
|
||||
|
||||
// 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->AddCallback(ImDrawCallback_ImCanvas, nullptr);
|
||||
|
||||
m_DrawListFirstCommandIndex = ImMax(m_DrawList->CmdBuffer.Size - 1, 0);
|
||||
|
||||
# if defined(IMGUI_HAS_VIEWPORT)
|
||||
auto window = ImGui::GetCurrentWindow();
|
||||
window->Pos = ImVec2(0.0f, 0.0f);
|
||||
|
||||
auto viewport_min = m_ViewportPosBackup;
|
||||
auto viewport_max = m_ViewportPosBackup + m_ViewportSizeBackup;
|
||||
|
||||
viewport_min.x = (viewport_min.x - m_ViewTransformPosition.x) * m_View.InvScale;
|
||||
viewport_min.y = (viewport_min.y - m_ViewTransformPosition.y) * m_View.InvScale;
|
||||
viewport_max.x = (viewport_max.x - m_ViewTransformPosition.x) * m_View.InvScale;
|
||||
viewport_max.y = (viewport_max.y - m_ViewTransformPosition.y) * m_View.InvScale;
|
||||
|
||||
auto viewport = ImGui::GetWindowViewport();
|
||||
viewport->Pos = viewport_min;
|
||||
viewport->Size = viewport_max - viewport_min;
|
||||
|
||||
# if IMGUI_VERSION_NUM > 18002
|
||||
viewport->WorkPos = m_ViewportWorkPosBackup * m_View.InvScale;
|
||||
viewport->WorkSize = m_ViewportWorkSizeBackup * m_View.InvScale;
|
||||
# else
|
||||
viewport->WorkOffsetMin = m_ViewportWorkOffsetMinBackup * m_View.InvScale;
|
||||
viewport->WorkOffsetMax = m_ViewportWorkOffsetMaxBackup * m_View.InvScale;
|
||||
# endif
|
||||
# endif
|
||||
|
||||
// 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 + ImVtxOffsetRef(m_DrawList);
|
||||
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 + ImVtxOffsetRef(m_DrawList);
|
||||
|
||||
// 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_DrawListFirstCommandIndex; 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_DrawListFirstCommandIndex; 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;
|
||||
}
|
||||
}
|
||||
|
||||
// Remove sentinel draw command if present
|
||||
if (m_DrawListCommadBufferSize > 0)
|
||||
{
|
||||
if (m_DrawList->CmdBuffer.size() > m_DrawListCommadBufferSize && m_DrawList->CmdBuffer[m_DrawListCommadBufferSize].UserCallback == ImDrawCallback_ImCanvas)
|
||||
m_DrawList->CmdBuffer.erase(m_DrawList->CmdBuffer.Data + m_DrawListCommadBufferSize);
|
||||
else if (m_DrawList->CmdBuffer.size() >= m_DrawListCommadBufferSize && m_DrawList->CmdBuffer[m_DrawListCommadBufferSize - 1].UserCallback == ImDrawCallback_ImCanvas)
|
||||
m_DrawList->CmdBuffer.erase(m_DrawList->CmdBuffer.Data + m_DrawListCommadBufferSize - 1);
|
||||
}
|
||||
|
||||
auto& fringeScale = ImFringeScaleRef(m_DrawList);
|
||||
fringeScale = m_LastFringeScale;
|
||||
|
||||
// And pop \o/
|
||||
ImGui::PopClipRect();
|
||||
|
||||
RestoreInputState();
|
||||
RestoreViewportState();
|
||||
}
|
||||
273
libs/node_editor/imgui_canvas.h
Normal file
273
libs/node_editor/imgui_canvas.h
Normal file
@@ -0,0 +1,273 @@
|
||||
// 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 using 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 using 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.
|
||||
//
|
||||
// VERSION 0.1
|
||||
//
|
||||
// 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
|
||||
|
||||
#ifndef IMGUIEX_CANVAS_API
|
||||
#define IMGUIEX_CANVAS_API
|
||||
#endif
|
||||
|
||||
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().
|
||||
IMGUIEX_CANVAS_API bool Begin(const char* id, const ImVec2& size);
|
||||
IMGUIEX_CANVAS_API bool Begin(ImGuiID id, const ImVec2& size);
|
||||
|
||||
// Ends interaction with canvas plane.
|
||||
//
|
||||
// Must be called only when Begin() retuned true.
|
||||
IMGUIEX_CANVAS_API 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.
|
||||
IMGUIEX_CANVAS_API void SetView(const ImVec2& origin, float scale);
|
||||
IMGUIEX_CANVAS_API 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.
|
||||
IMGUIEX_CANVAS_API void CenterView(const ImVec2& canvasPoint);
|
||||
|
||||
// Calculates view over specific point on canvas plane.
|
||||
IMGUIEX_CANVAS_API 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.
|
||||
IMGUIEX_CANVAS_API void CenterView(const ImRect& canvasRect);
|
||||
|
||||
// Calculates view over specific rectangle on canvas plane.
|
||||
IMGUIEX_CANVAS_API 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().
|
||||
IMGUIEX_CANVAS_API void Suspend();
|
||||
IMGUIEX_CANVAS_API void Resume();
|
||||
|
||||
// Transforms point from canvas plane to ImGui.
|
||||
IMGUIEX_CANVAS_API ImVec2 FromLocal(const ImVec2& point) const;
|
||||
IMGUIEX_CANVAS_API ImVec2 FromLocal(const ImVec2& point, const CanvasView& view) const;
|
||||
|
||||
// Transforms vector from canvas plant to ImGui.
|
||||
IMGUIEX_CANVAS_API ImVec2 FromLocalV(const ImVec2& vector) const;
|
||||
IMGUIEX_CANVAS_API ImVec2 FromLocalV(const ImVec2& vector, const CanvasView& view) const;
|
||||
|
||||
// Transforms point from ImGui to canvas plane.
|
||||
IMGUIEX_CANVAS_API ImVec2 ToLocal(const ImVec2& point) const;
|
||||
IMGUIEX_CANVAS_API ImVec2 ToLocal(const ImVec2& point, const CanvasView& view) const;
|
||||
|
||||
// Transforms vector from ImGui to canvas plane.
|
||||
IMGUIEX_CANVAS_API ImVec2 ToLocalV(const ImVec2& vector) const;
|
||||
IMGUIEX_CANVAS_API 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.
|
||||
IMGUIEX_CANVAS_API 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 SaveViewportState();
|
||||
void RestoreViewportState();
|
||||
|
||||
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_DrawListFirstCommandIndex = 0;
|
||||
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;
|
||||
|
||||
# if defined(IMGUI_HAS_VIEWPORT)
|
||||
ImVec2 m_WindowPosBackup;
|
||||
ImVec2 m_ViewportPosBackup;
|
||||
ImVec2 m_ViewportSizeBackup;
|
||||
# if IMGUI_VERSION_NUM > 18002
|
||||
ImVec2 m_ViewportWorkPosBackup;
|
||||
ImVec2 m_ViewportWorkSizeBackup;
|
||||
# else
|
||||
ImVec2 m_ViewportWorkOffsetMinBackup;
|
||||
ImVec2 m_ViewportWorkOffsetMaxBackup;
|
||||
# endif
|
||||
# endif
|
||||
};
|
||||
|
||||
} // namespace ImGuiEx
|
||||
|
||||
# endif // __IMGUI_EX_CANVAS_H__
|
||||
77
libs/node_editor/imgui_extra_math.h
Normal file
77
libs/node_editor/imgui_extra_math.h
Normal file
@@ -0,0 +1,77 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// VERSION 0.9.1
|
||||
//
|
||||
// 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
|
||||
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
# ifndef IMGUI_DEFINE_MATH_OPERATORS
|
||||
# define IMGUI_DEFINE_MATH_OPERATORS
|
||||
# endif
|
||||
# include <imgui.h>
|
||||
# include <imgui_internal.h>
|
||||
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
struct ImLine
|
||||
{
|
||||
ImVec2 A, B;
|
||||
};
|
||||
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
# if IMGUI_VERSION_NUM < 19002
|
||||
inline bool operator==(const ImVec2& lhs, const ImVec2& rhs);
|
||||
inline bool operator!=(const ImVec2& lhs, const ImVec2& rhs);
|
||||
# endif
|
||||
inline ImVec2 operator*(const float lhs, const ImVec2& rhs);
|
||||
# if IMGUI_VERSION_NUM < 18955
|
||||
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__
|
||||
193
libs/node_editor/imgui_extra_math.inl
Normal file
193
libs/node_editor/imgui_extra_math.inl
Normal file
@@ -0,0 +1,193 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// VERSION 0.9.1
|
||||
//
|
||||
// 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"
|
||||
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
# if IMGUI_VERSION_NUM < 19002
|
||||
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;
|
||||
}
|
||||
# endif
|
||||
|
||||
inline ImVec2 operator*(const float lhs, const ImVec2& rhs)
|
||||
{
|
||||
return ImVec2(lhs * rhs.x, lhs * rhs.y);
|
||||
}
|
||||
|
||||
# if IMGUI_VERSION_NUM < 18955
|
||||
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__
|
||||
5855
libs/node_editor/imgui_node_editor.cpp
Normal file
5855
libs/node_editor/imgui_node_editor.cpp
Normal file
File diff suppressed because it is too large
Load Diff
530
libs/node_editor/imgui_node_editor.h
Normal file
530
libs/node_editor/imgui_node_editor.h
Normal file
@@ -0,0 +1,530 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// VERSION 0.9.1
|
||||
//
|
||||
// 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
|
||||
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
# define IMGUI_NODE_EDITOR_VERSION "0.9.4"
|
||||
# define IMGUI_NODE_EDITOR_VERSION_NUM 000904
|
||||
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
#ifndef IMGUI_NODE_EDITOR_API
|
||||
#define IMGUI_NODE_EDITOR_API
|
||||
#endif
|
||||
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
namespace ax {
|
||||
namespace NodeEditor {
|
||||
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
struct NodeId;
|
||||
struct LinkId;
|
||||
struct PinId;
|
||||
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
enum class PinKind
|
||||
{
|
||||
Input,
|
||||
Output
|
||||
};
|
||||
|
||||
enum class FlowDirection
|
||||
{
|
||||
Forward,
|
||||
Backward
|
||||
};
|
||||
|
||||
enum class CanvasSizeMode
|
||||
{
|
||||
FitVerticalView, // Previous view will be scaled to fit new view on Y axis
|
||||
FitHorizontalView, // Previous view will be scaled to fit new view on X axis
|
||||
CenterOnly, // Previous view will be centered on new view
|
||||
};
|
||||
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
enum class SaveReasonFlags: uint32_t
|
||||
{
|
||||
None = 0x00000000,
|
||||
Navigation = 0x00000001,
|
||||
Position = 0x00000002,
|
||||
Size = 0x00000004,
|
||||
Selection = 0x00000008,
|
||||
AddNode = 0x00000010,
|
||||
RemoveNode = 0x00000020,
|
||||
User = 0x00000040
|
||||
};
|
||||
|
||||
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
|
||||
{
|
||||
using CanvasSizeModeAlias = ax::NodeEditor::CanvasSizeMode;
|
||||
|
||||
const char* SettingsFile;
|
||||
ConfigSession BeginSaveSession;
|
||||
ConfigSession EndSaveSession;
|
||||
ConfigSaveSettings SaveSettings;
|
||||
ConfigLoadSettings LoadSettings;
|
||||
ConfigSaveNodeSettings SaveNodeSettings;
|
||||
ConfigLoadNodeSettings LoadNodeSettings;
|
||||
void* UserPointer;
|
||||
ImVector<float> CustomZoomLevels;
|
||||
CanvasSizeModeAlias CanvasSizeMode;
|
||||
int DragButtonIndex; // Mouse button index drag action will react to (0-left, 1-right, 2-middle)
|
||||
int SelectButtonIndex; // Mouse button index select action will react to (0-left, 1-right, 2-middle)
|
||||
int NavigateButtonIndex; // Mouse button index navigate action will react to (0-left, 1-right, 2-middle)
|
||||
int ContextMenuButtonIndex; // Mouse button index context menu action will react to (0-left, 1-right, 2-middle)
|
||||
bool EnableSmoothZoom;
|
||||
float SmoothZoomPower;
|
||||
|
||||
Config()
|
||||
: SettingsFile("NodeEditor.json")
|
||||
, BeginSaveSession(nullptr)
|
||||
, EndSaveSession(nullptr)
|
||||
, SaveSettings(nullptr)
|
||||
, LoadSettings(nullptr)
|
||||
, SaveNodeSettings(nullptr)
|
||||
, LoadNodeSettings(nullptr)
|
||||
, UserPointer(nullptr)
|
||||
, CustomZoomLevels()
|
||||
, CanvasSizeMode(CanvasSizeModeAlias::FitVerticalView)
|
||||
, DragButtonIndex(0)
|
||||
, SelectButtonIndex(0)
|
||||
, NavigateButtonIndex(1)
|
||||
, ContextMenuButtonIndex(1)
|
||||
, EnableSmoothZoom(false)
|
||||
# ifdef __APPLE__
|
||||
, SmoothZoomPower(1.1f)
|
||||
# else
|
||||
, SmoothZoomPower(1.3f)
|
||||
# endif
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
enum StyleColor
|
||||
{
|
||||
StyleColor_Bg,
|
||||
StyleColor_Grid,
|
||||
StyleColor_NodeBg,
|
||||
StyleColor_NodeBorder,
|
||||
StyleColor_HovNodeBorder,
|
||||
StyleColor_SelNodeBorder,
|
||||
StyleColor_NodeSelRect,
|
||||
StyleColor_NodeSelRectBorder,
|
||||
StyleColor_HovLinkBorder,
|
||||
StyleColor_SelLinkBorder,
|
||||
StyleColor_HighlightLinkBorder,
|
||||
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,
|
||||
StyleVar_HighlightConnectedLinks,
|
||||
StyleVar_SnapLinkToPinDir,
|
||||
StyleVar_HoveredNodeBorderOffset,
|
||||
StyleVar_SelectedNodeBorderOffset,
|
||||
|
||||
StyleVar_Count
|
||||
};
|
||||
|
||||
struct Style
|
||||
{
|
||||
ImVec4 NodePadding;
|
||||
float NodeRounding;
|
||||
float NodeBorderWidth;
|
||||
float HoveredNodeBorderWidth;
|
||||
float HoverNodeBorderOffset;
|
||||
float SelectedNodeBorderWidth;
|
||||
float SelectedNodeBorderOffset;
|
||||
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;
|
||||
float HighlightConnectedLinks;
|
||||
float SnapLinkToPinDir; // when true link will start on the line defined by pin direction
|
||||
ImVec4 Colors[StyleColor_Count];
|
||||
|
||||
Style()
|
||||
{
|
||||
NodePadding = ImVec4(8, 8, 8, 8);
|
||||
NodeRounding = 12.0f;
|
||||
NodeBorderWidth = 1.5f;
|
||||
HoveredNodeBorderWidth = 3.5f;
|
||||
HoverNodeBorderOffset = 0.0f;
|
||||
SelectedNodeBorderWidth = 3.5f;
|
||||
SelectedNodeBorderOffset = 0.0f;
|
||||
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(0.0f, 0.0f);
|
||||
PivotScale = ImVec2(1, 1);
|
||||
#if IMGUI_VERSION_NUM > 18101
|
||||
PinCorners = ImDrawFlags_RoundCornersAll;
|
||||
#else
|
||||
PinCorners = ImDrawCornerFlags_All;
|
||||
#endif
|
||||
PinRadius = 0.0f;
|
||||
PinArrowSize = 0.0f;
|
||||
PinArrowWidth = 0.0f;
|
||||
GroupRounding = 6.0f;
|
||||
GroupBorderWidth = 1.0f;
|
||||
HighlightConnectedLinks = 0.0f;
|
||||
SnapLinkToPinDir = 0.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_HighlightLinkBorder]= ImColor(204, 105, 0, 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;
|
||||
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
IMGUI_NODE_EDITOR_API void SetCurrentEditor(EditorContext* ctx);
|
||||
IMGUI_NODE_EDITOR_API EditorContext* GetCurrentEditor();
|
||||
IMGUI_NODE_EDITOR_API EditorContext* CreateEditor(const Config* config = nullptr);
|
||||
IMGUI_NODE_EDITOR_API void DestroyEditor(EditorContext* ctx);
|
||||
IMGUI_NODE_EDITOR_API const Config& GetConfig(EditorContext* ctx = nullptr);
|
||||
|
||||
IMGUI_NODE_EDITOR_API Style& GetStyle();
|
||||
IMGUI_NODE_EDITOR_API const char* GetStyleColorName(StyleColor colorIndex);
|
||||
|
||||
IMGUI_NODE_EDITOR_API void PushStyleColor(StyleColor colorIndex, const ImVec4& color);
|
||||
IMGUI_NODE_EDITOR_API void PopStyleColor(int count = 1);
|
||||
|
||||
IMGUI_NODE_EDITOR_API void PushStyleVar(StyleVar varIndex, float value);
|
||||
IMGUI_NODE_EDITOR_API void PushStyleVar(StyleVar varIndex, const ImVec2& value);
|
||||
IMGUI_NODE_EDITOR_API void PushStyleVar(StyleVar varIndex, const ImVec4& value);
|
||||
IMGUI_NODE_EDITOR_API void PopStyleVar(int count = 1);
|
||||
|
||||
IMGUI_NODE_EDITOR_API void Begin(const char* id, const ImVec2& size = ImVec2(0, 0));
|
||||
IMGUI_NODE_EDITOR_API void End();
|
||||
|
||||
IMGUI_NODE_EDITOR_API void BeginNode(NodeId id);
|
||||
IMGUI_NODE_EDITOR_API void BeginPin(PinId id, PinKind kind);
|
||||
IMGUI_NODE_EDITOR_API void PinRect(const ImVec2& a, const ImVec2& b);
|
||||
IMGUI_NODE_EDITOR_API void PinPivotRect(const ImVec2& a, const ImVec2& b);
|
||||
IMGUI_NODE_EDITOR_API void PinPivotSize(const ImVec2& size);
|
||||
IMGUI_NODE_EDITOR_API void PinPivotScale(const ImVec2& scale);
|
||||
IMGUI_NODE_EDITOR_API void PinPivotAlignment(const ImVec2& alignment);
|
||||
IMGUI_NODE_EDITOR_API void EndPin();
|
||||
IMGUI_NODE_EDITOR_API void Group(const ImVec2& size);
|
||||
IMGUI_NODE_EDITOR_API void EndNode();
|
||||
|
||||
IMGUI_NODE_EDITOR_API bool BeginGroupHint(NodeId nodeId);
|
||||
IMGUI_NODE_EDITOR_API ImVec2 GetGroupMin();
|
||||
IMGUI_NODE_EDITOR_API ImVec2 GetGroupMax();
|
||||
IMGUI_NODE_EDITOR_API ImDrawList* GetHintForegroundDrawList();
|
||||
IMGUI_NODE_EDITOR_API ImDrawList* GetHintBackgroundDrawList();
|
||||
IMGUI_NODE_EDITOR_API void EndGroupHint();
|
||||
|
||||
// TODO: Add a way to manage node background channels
|
||||
IMGUI_NODE_EDITOR_API ImDrawList* GetNodeBackgroundDrawList(NodeId nodeId);
|
||||
|
||||
IMGUI_NODE_EDITOR_API bool Link(LinkId id, PinId startPinId, PinId endPinId, const ImVec4& color = ImVec4(1, 1, 1, 1), float thickness = 1.0f);
|
||||
|
||||
IMGUI_NODE_EDITOR_API void Flow(LinkId linkId, FlowDirection direction = FlowDirection::Forward);
|
||||
|
||||
IMGUI_NODE_EDITOR_API bool BeginCreate(const ImVec4& color = ImVec4(1, 1, 1, 1), float thickness = 1.0f);
|
||||
IMGUI_NODE_EDITOR_API bool QueryNewLink(PinId* startId, PinId* endId);
|
||||
IMGUI_NODE_EDITOR_API bool QueryNewLink(PinId* startId, PinId* endId, const ImVec4& color, float thickness = 1.0f);
|
||||
IMGUI_NODE_EDITOR_API bool QueryNewNode(PinId* pinId);
|
||||
IMGUI_NODE_EDITOR_API bool QueryNewNode(PinId* pinId, const ImVec4& color, float thickness = 1.0f);
|
||||
IMGUI_NODE_EDITOR_API bool AcceptNewItem();
|
||||
IMGUI_NODE_EDITOR_API bool AcceptNewItem(const ImVec4& color, float thickness = 1.0f);
|
||||
IMGUI_NODE_EDITOR_API void RejectNewItem();
|
||||
IMGUI_NODE_EDITOR_API void RejectNewItem(const ImVec4& color, float thickness = 1.0f);
|
||||
IMGUI_NODE_EDITOR_API void EndCreate();
|
||||
|
||||
IMGUI_NODE_EDITOR_API bool BeginDelete();
|
||||
IMGUI_NODE_EDITOR_API bool QueryDeletedLink(LinkId* linkId, PinId* startId = nullptr, PinId* endId = nullptr);
|
||||
IMGUI_NODE_EDITOR_API bool QueryDeletedNode(NodeId* nodeId);
|
||||
IMGUI_NODE_EDITOR_API bool AcceptDeletedItem(bool deleteDependencies = true);
|
||||
IMGUI_NODE_EDITOR_API void RejectDeletedItem();
|
||||
IMGUI_NODE_EDITOR_API void EndDelete();
|
||||
|
||||
IMGUI_NODE_EDITOR_API void SetNodePosition(NodeId nodeId, const ImVec2& editorPosition);
|
||||
IMGUI_NODE_EDITOR_API void SetGroupSize(NodeId nodeId, const ImVec2& size);
|
||||
IMGUI_NODE_EDITOR_API ImVec2 GetNodePosition(NodeId nodeId);
|
||||
IMGUI_NODE_EDITOR_API ImVec2 GetNodeSize(NodeId nodeId);
|
||||
IMGUI_NODE_EDITOR_API void CenterNodeOnScreen(NodeId nodeId);
|
||||
IMGUI_NODE_EDITOR_API void SetNodeZPosition(NodeId nodeId, float z); // Sets node z position, nodes with higher value are drawn over nodes with lower value
|
||||
IMGUI_NODE_EDITOR_API float GetNodeZPosition(NodeId nodeId); // Returns node z position, defaults is 0.0f
|
||||
|
||||
IMGUI_NODE_EDITOR_API void RestoreNodeState(NodeId nodeId);
|
||||
|
||||
IMGUI_NODE_EDITOR_API void Suspend();
|
||||
IMGUI_NODE_EDITOR_API void Resume();
|
||||
IMGUI_NODE_EDITOR_API bool IsSuspended();
|
||||
|
||||
IMGUI_NODE_EDITOR_API bool IsActive();
|
||||
|
||||
IMGUI_NODE_EDITOR_API bool HasSelectionChanged();
|
||||
IMGUI_NODE_EDITOR_API int GetSelectedObjectCount();
|
||||
IMGUI_NODE_EDITOR_API int GetSelectedNodes(NodeId* nodes, int size);
|
||||
IMGUI_NODE_EDITOR_API int GetSelectedLinks(LinkId* links, int size);
|
||||
IMGUI_NODE_EDITOR_API bool IsNodeSelected(NodeId nodeId);
|
||||
IMGUI_NODE_EDITOR_API bool IsLinkSelected(LinkId linkId);
|
||||
IMGUI_NODE_EDITOR_API void ClearSelection();
|
||||
IMGUI_NODE_EDITOR_API void SelectNode(NodeId nodeId, bool append = false);
|
||||
IMGUI_NODE_EDITOR_API void SelectLink(LinkId linkId, bool append = false);
|
||||
IMGUI_NODE_EDITOR_API void DeselectNode(NodeId nodeId);
|
||||
IMGUI_NODE_EDITOR_API void DeselectLink(LinkId linkId);
|
||||
|
||||
IMGUI_NODE_EDITOR_API bool DeleteNode(NodeId nodeId);
|
||||
IMGUI_NODE_EDITOR_API bool DeleteLink(LinkId linkId);
|
||||
|
||||
IMGUI_NODE_EDITOR_API bool HasAnyLinks(NodeId nodeId); // Returns true if node has any link connected
|
||||
IMGUI_NODE_EDITOR_API bool HasAnyLinks(PinId pinId); // Return true if pin has any link connected
|
||||
IMGUI_NODE_EDITOR_API int BreakLinks(NodeId nodeId); // Break all links connected to this node
|
||||
IMGUI_NODE_EDITOR_API int BreakLinks(PinId pinId); // Break all links connected to this pin
|
||||
|
||||
IMGUI_NODE_EDITOR_API void NavigateToContent(float duration = -1);
|
||||
IMGUI_NODE_EDITOR_API void NavigateToSelection(bool zoomIn = false, float duration = -1);
|
||||
|
||||
IMGUI_NODE_EDITOR_API bool ShowNodeContextMenu(NodeId* nodeId);
|
||||
IMGUI_NODE_EDITOR_API bool ShowPinContextMenu(PinId* pinId);
|
||||
IMGUI_NODE_EDITOR_API bool ShowLinkContextMenu(LinkId* linkId);
|
||||
IMGUI_NODE_EDITOR_API bool ShowBackgroundContextMenu();
|
||||
|
||||
IMGUI_NODE_EDITOR_API void EnableShortcuts(bool enable);
|
||||
IMGUI_NODE_EDITOR_API bool AreShortcutsEnabled();
|
||||
|
||||
IMGUI_NODE_EDITOR_API bool BeginShortcut();
|
||||
IMGUI_NODE_EDITOR_API bool AcceptCut();
|
||||
IMGUI_NODE_EDITOR_API bool AcceptCopy();
|
||||
IMGUI_NODE_EDITOR_API bool AcceptPaste();
|
||||
IMGUI_NODE_EDITOR_API bool AcceptDuplicate();
|
||||
IMGUI_NODE_EDITOR_API bool AcceptCreateNode();
|
||||
IMGUI_NODE_EDITOR_API int GetActionContextSize();
|
||||
IMGUI_NODE_EDITOR_API int GetActionContextNodes(NodeId* nodes, int size);
|
||||
IMGUI_NODE_EDITOR_API int GetActionContextLinks(LinkId* links, int size);
|
||||
IMGUI_NODE_EDITOR_API void EndShortcut();
|
||||
|
||||
IMGUI_NODE_EDITOR_API float GetCurrentZoom();
|
||||
|
||||
IMGUI_NODE_EDITOR_API NodeId GetHoveredNode();
|
||||
IMGUI_NODE_EDITOR_API PinId GetHoveredPin();
|
||||
IMGUI_NODE_EDITOR_API LinkId GetHoveredLink();
|
||||
IMGUI_NODE_EDITOR_API NodeId GetDoubleClickedNode();
|
||||
IMGUI_NODE_EDITOR_API PinId GetDoubleClickedPin();
|
||||
IMGUI_NODE_EDITOR_API LinkId GetDoubleClickedLink();
|
||||
IMGUI_NODE_EDITOR_API bool IsBackgroundClicked();
|
||||
IMGUI_NODE_EDITOR_API bool IsBackgroundDoubleClicked();
|
||||
IMGUI_NODE_EDITOR_API ImGuiMouseButton GetBackgroundClickButtonIndex(); // -1 if none
|
||||
IMGUI_NODE_EDITOR_API ImGuiMouseButton GetBackgroundDoubleClickButtonIndex(); // -1 if none
|
||||
|
||||
IMGUI_NODE_EDITOR_API bool GetLinkPins(LinkId linkId, PinId* startPinId, PinId* endPinId); // pass nullptr if particular pin do not interest you
|
||||
|
||||
IMGUI_NODE_EDITOR_API bool PinHadAnyLinks(PinId pinId);
|
||||
|
||||
IMGUI_NODE_EDITOR_API ImVec2 GetScreenSize();
|
||||
IMGUI_NODE_EDITOR_API ImVec2 ScreenToCanvas(const ImVec2& pos);
|
||||
IMGUI_NODE_EDITOR_API ImVec2 CanvasToScreen(const ImVec2& pos);
|
||||
|
||||
IMGUI_NODE_EDITOR_API int GetNodeCount(); // Returns number of submitted nodes since Begin() call
|
||||
IMGUI_NODE_EDITOR_API int GetOrderedNodeIds(NodeId* nodes, int size); // Fills an array with node id's in order they're drawn; up to 'size` elements are set. Returns actual size of filled id's.
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
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__
|
||||
762
libs/node_editor/imgui_node_editor_api.cpp
Normal file
762
libs/node_editor/imgui_node_editor_api.cpp
Normal file
@@ -0,0 +1,762 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// VERSION 0.9.1
|
||||
//
|
||||
// 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)
|
||||
{
|
||||
auto lastContext = GetCurrentEditor();
|
||||
|
||||
// Set context we're about to destroy as current, to give callback valid context
|
||||
if (lastContext != ctx)
|
||||
SetCurrentEditor(ctx);
|
||||
|
||||
auto editor = reinterpret_cast<ax::NodeEditor::Detail::EditorContext*>(ctx);
|
||||
|
||||
delete editor;
|
||||
|
||||
if (lastContext != ctx)
|
||||
SetCurrentEditor(lastContext);
|
||||
}
|
||||
|
||||
const ax::NodeEditor::Config& ax::NodeEditor::GetConfig(EditorContext* ctx)
|
||||
{
|
||||
if (ctx == nullptr)
|
||||
ctx = GetCurrentEditor();
|
||||
|
||||
if (ctx)
|
||||
{
|
||||
auto editor = reinterpret_cast<ax::NodeEditor::Detail::EditorContext*>(ctx);
|
||||
|
||||
return editor->GetConfig();
|
||||
}
|
||||
else
|
||||
{
|
||||
static Config s_EmptyConfig;
|
||||
return s_EmptyConfig;
|
||||
}
|
||||
}
|
||||
|
||||
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, FlowDirection direction)
|
||||
{
|
||||
if (auto link = s_Editor->FindLink(linkId))
|
||||
s_Editor->Flow(link, direction);
|
||||
}
|
||||
|
||||
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(bool deleteDependencies)
|
||||
{
|
||||
auto& context = s_Editor->GetItemDeleter();
|
||||
|
||||
return context.AcceptItem(deleteDependencies);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
void ax::NodeEditor::SetGroupSize(NodeId nodeId, const ImVec2& size)
|
||||
{
|
||||
s_Editor->SetGroupSize(nodeId, size);
|
||||
}
|
||||
|
||||
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::SetNodeZPosition(NodeId nodeId, float z)
|
||||
{
|
||||
s_Editor->SetNodeZPosition(nodeId, z);
|
||||
}
|
||||
|
||||
float ax::NodeEditor::GetNodeZPosition(NodeId nodeId)
|
||||
{
|
||||
return s_Editor->GetNodeZPosition(nodeId);
|
||||
}
|
||||
|
||||
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->IsFocused();
|
||||
}
|
||||
|
||||
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;
|
||||
});
|
||||
}
|
||||
|
||||
bool ax::NodeEditor::IsNodeSelected(NodeId nodeId)
|
||||
{
|
||||
if (auto node = s_Editor->FindNode(nodeId))
|
||||
return s_Editor->IsSelected(node);
|
||||
else
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ax::NodeEditor::IsLinkSelected(LinkId linkId)
|
||||
{
|
||||
if (auto link = s_Editor->FindLink(linkId))
|
||||
return s_Editor->IsSelected(link);
|
||||
else
|
||||
return false;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
bool ax::NodeEditor::HasAnyLinks(NodeId nodeId)
|
||||
{
|
||||
return s_Editor->HasAnyLinks(nodeId);
|
||||
}
|
||||
|
||||
bool ax::NodeEditor::HasAnyLinks(PinId pinId)
|
||||
{
|
||||
return s_Editor->HasAnyLinks(pinId);
|
||||
}
|
||||
|
||||
int ax::NodeEditor::BreakLinks(NodeId nodeId)
|
||||
{
|
||||
return s_Editor->BreakLinks(nodeId);
|
||||
}
|
||||
|
||||
int ax::NodeEditor::BreakLinks(PinId pinId)
|
||||
{
|
||||
return s_Editor->BreakLinks(pinId);
|
||||
}
|
||||
|
||||
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::GetHoveredNode()
|
||||
{
|
||||
return s_Editor->GetHoveredNode();
|
||||
}
|
||||
|
||||
ax::NodeEditor::PinId ax::NodeEditor::GetHoveredPin()
|
||||
{
|
||||
return s_Editor->GetHoveredPin();
|
||||
}
|
||||
|
||||
ax::NodeEditor::LinkId ax::NodeEditor::GetHoveredLink()
|
||||
{
|
||||
return s_Editor->GetHoveredLink();
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
ImGuiMouseButton ax::NodeEditor::GetBackgroundClickButtonIndex()
|
||||
{
|
||||
return s_Editor->GetBackgroundClickButtonIndex();
|
||||
}
|
||||
|
||||
ImGuiMouseButton ax::NodeEditor::GetBackgroundDoubleClickButtonIndex()
|
||||
{
|
||||
return s_Editor->GetBackgroundDoubleClickButtonIndex();
|
||||
}
|
||||
|
||||
bool ax::NodeEditor::GetLinkPins(LinkId linkId, PinId* startPinId, PinId* endPinId)
|
||||
{
|
||||
auto link = s_Editor->FindLink(linkId);
|
||||
if (!link)
|
||||
return false;
|
||||
|
||||
if (startPinId)
|
||||
*startPinId = link->m_StartPin->m_ID;
|
||||
if (endPinId)
|
||||
*endPinId = link->m_EndPin->m_ID;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
int ax::NodeEditor::GetNodeCount()
|
||||
{
|
||||
return s_Editor->CountLiveNodes();
|
||||
}
|
||||
|
||||
int ax::NodeEditor::GetOrderedNodeIds(NodeId* nodes, int size)
|
||||
{
|
||||
return s_Editor->GetNodeIds(nodes, size);
|
||||
}
|
||||
1560
libs/node_editor/imgui_node_editor_internal.h
Normal file
1560
libs/node_editor/imgui_node_editor_internal.h
Normal file
File diff suppressed because it is too large
Load Diff
65
libs/node_editor/imgui_node_editor_internal.inl
Normal file
65
libs/node_editor/imgui_node_editor_internal.inl
Normal file
@@ -0,0 +1,65 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// VERSION 0.9.1
|
||||
//
|
||||
// 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());
|
||||
}
|
||||
|
||||
inline ImVec2 ImGui_GetMouseClickPos(ImGuiMouseButton buttonIndex)
|
||||
{
|
||||
if (ImGui::IsMouseDown(buttonIndex))
|
||||
return ImGui::GetIO().MouseClickedPos[buttonIndex];
|
||||
else
|
||||
return ImGui::GetMousePos();
|
||||
}
|
||||
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
} // namespace Detail
|
||||
} // namespace Editor
|
||||
} // namespace ax
|
||||
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
# endif // __IMGUI_NODE_EDITOR_INTERNAL_INL__
|
||||
Reference in New Issue
Block a user