#include "inspect.hpp" #include "compat/assert.hpp" #include "compat/defs.hpp" #include "entity/accessor.hpp" #include "imgui-raii.hpp" #include #include #include #include #include #include #include #include namespace floormat::entities { namespace { const char* label_left(StringView label, char* buf, std::size_t len) { std::snprintf(buf, len, "##%s", label.data()); float width = ImGui::CalcItemWidth(), x = ImGui::GetCursorPosX(); ImGui::Text("%s", label.data()); ImGui::SameLine(); ImGui::SetCursorPosX(x + width*.5f + ImGui::GetStyle().ItemInnerSpacing.x); ImGui::SetNextItemWidth(-1); return buf; } template struct IGDT_; template<> struct IGDT_ : std::integral_constant {}; template<> struct IGDT_ : std::integral_constant {}; template<> struct IGDT_ : std::integral_constant {}; template<> struct IGDT_ : std::integral_constant {}; template<> struct IGDT_ : std::integral_constant {}; template<> struct IGDT_ : std::integral_constant {}; template<> struct IGDT_ : std::integral_constant {}; template constexpr auto IGDT = IGDT_::value; using namespace imgui; using namespace entities; template requires std::is_integral_v constexpr bool eqv(T a, T b) { return a == b; } inline bool eqv(float a, float b) { return std::fabs(a - b) < 1e-8f; } inline bool eqv(const String& a, const String& b) { return a == b; } template constexpr bool eqv(const Math::Vector& a, const Math::Vector& b) { return a == b; } int corrade_string_resize_callback(ImGuiInputTextCallbackData* data) { if (data->EventFlag == ImGuiInputTextFlags_CallbackResize) { auto* my_str = reinterpret_cast(data->UserData); fm_assert(my_str->begin() == data->Buf); *my_str = String{ValueInit, (std::size_t)data->BufSize}; data->Buf = my_str->begin(); } return 0; } template bool do_inspect_field(void* datum, const erased_accessor& accessor, field_repr repr, const ArrayView>& list) { if (list.isEmpty()) fm_assert(accessor.check_field_type()); fm_assert(!list.isEmpty() == (repr == field_repr::cbx)); bool should_disable; char buf[128]; switch (accessor.is_enabled(datum)) { using enum field_status; case hidden: return false; case readonly: should_disable = true; break; case enabled: should_disable = false; break; } should_disable = should_disable || !accessor.can_write(); [[maybe_unused]] auto disabler = begin_disabled(should_disable); bool ret = false; const char* const label = label_left(accessor.field_name, buf, sizeof buf); T value{}; accessor.read_fun(datum, accessor.reader, &value); auto orig = value; if constexpr(std::is_same_v) ret = ImGui::InputText(label, const_cast(value.data()), value.size(), ImGuiInputTextFlags_ReadOnly); else if constexpr(std::is_same_v) { ret = ImGui::InputText(label, value.begin(), value.size(), ImGuiInputTextFlags_CallbackResize, corrade_string_resize_callback, &value); if (auto max_len = accessor.get_max_length(datum); value.size() > max_len) value = value.prefix(max_len); } else if constexpr(std::is_same_v) ret = ImGui::Checkbox(label, &value); else if constexpr (!Math::IsVector()) { auto [min, max] = accessor.get_range(datum).convert(); constexpr auto igdt = IGDT; constexpr T step(!std::is_floating_point_v ? T(1) : T(1e-6f)), step2(!std::is_floating_point_v ? T(10) : T(1e-3f)); switch (repr) { default: fm_warn_once("invalid repr enum value '%zu'", (std::size_t)repr); break; case field_repr::input: ret = ImGui::InputScalar(label, igdt, &value, &step, &step2); break; case field_repr::slider: ret = ImGui::SliderScalar(label, igdt, &value, &min, &max); break; case field_repr::drag: ret = ImGui::DragScalar(label, igdt, &value, 1, &min, &max); break; case field_repr::cbx: { if constexpr(std::is_integral_v) { const char* preview = ""; const auto old_value = (std::size_t)static_cast>(value); for (const auto& [str, x] : list) if (x == old_value) { preview = str.data(); break; } if (auto b = begin_combo(label, preview)) for (const auto& [str, x] : list) { const bool is_selected = x == (std::size_t)old_value; if (ImGui::Selectable(str.data(), is_selected)) value = T(x), ret = true; if (is_selected) ImGui::SetItemDefaultFocus(); } break; } } } value = std::clamp(value, min, max); } else { using U = typename T::Type; auto [min, max] = accessor.get_range(datum).convert(); constexpr auto igdt = IGDT; constexpr T step(!std::is_floating_point_v ? U(1) : U(1e-6f)), step2(!std::is_floating_point_v ? U(10) : U(1e-3f)); switch (repr) { default: fm_warn_once("invalid repr enum value '%zu'", (std::size_t)repr); break; case field_repr::input: ret = ImGui::InputScalarN(label, igdt, &value, T::Size, &step, &step2); break; case field_repr::drag: fm_warn_once("can't use imgui input drag mode for vector type"); [[fallthrough]]; case field_repr::slider: ret = ImGui::SliderScalarN(label, igdt, &value, T::Size, &min, &max); break; } for (auto i = 0_uz; i < T::Size; i++) value[i] = std::clamp(value[i], min[i], max[i]); } if (ret && !should_disable && !eqv(value, orig)) if (accessor.is_enabled(datum) >= field_status::enabled && accessor.can_write()) { accessor.write_fun(datum, accessor.writer, &value); return true; } return false; } } // namespace #define MAKE_SPEC(type, repr) \ template<> \ bool inspect_field(void* datum, const erased_accessor& accessor, \ const ArrayView>& list) \ { \ return do_inspect_field(datum, accessor, (repr), list); \ } #define MAKE_SPEC2(type, repr) \ template<> \ bool inspect_field>(void* datum, const erased_accessor& accessor, \ const ArrayView>& list) \ { \ return do_inspect_field(datum, accessor, (repr), list); \ } #define MAKE_SPEC_REPRS(type) \ MAKE_SPEC2(type, field_repr::input) \ MAKE_SPEC2(type, field_repr::slider) \ MAKE_SPEC2(type, field_repr::drag) \ MAKE_SPEC(type, field_repr::input) #define MAKE_SPEC_REPRS2(type) \ MAKE_SPEC_REPRS(Math::Vector2) \ MAKE_SPEC_REPRS(Math::Vector3) \ MAKE_SPEC_REPRS(Math::Vector4) \ MAKE_SPEC_REPRS(type) \ MAKE_SPEC2(type, field_repr::cbx) MAKE_SPEC_REPRS2(std::uint8_t) MAKE_SPEC_REPRS2(std::int8_t) MAKE_SPEC_REPRS2(std::uint16_t) MAKE_SPEC_REPRS2(std::int16_t) MAKE_SPEC_REPRS2(std::uint32_t) MAKE_SPEC_REPRS2(std::int32_t) MAKE_SPEC_REPRS2(float) MAKE_SPEC(bool, field_repr::input) MAKE_SPEC(String, field_repr::input) MAKE_SPEC(StringView, field_repr::input) } // namespace floormat::entities