feat: update to 068e90b418b7da440a15db6495f2a37239d30bff

This commit is contained in:
2024-09-08 18:52:23 -05:00
parent c461e29eaa
commit 58b5f87a95
66 changed files with 41337 additions and 5176 deletions

21
libs/node_editor/LICENSE Normal file
View File

@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2019 Michał Cichoń
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View 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

View 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__

View 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__

View 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__

View 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();
}

View 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__

View 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__

View 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__

File diff suppressed because it is too large Load Diff

View 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__

View 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);
}

File diff suppressed because it is too large Load Diff

View 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__