Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

imgui support for bespoke interfaces #100

Merged
merged 1 commit into from
Oct 13, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
57 changes: 57 additions & 0 deletions cmake/dependencies/imgui-CMakeLists.txt.in
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
project(imgui LANGUAGES CXX)
if(CMAKE_SOURCE_DIR STREQUAL PROJECT_SOURCE_DIR)
# If top level project
SET(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/lib/${CMAKE_BUILD_TYPE}/)
else()
# If called via add_subdirectory()
SET(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/../lib/${CMAKE_BUILD_TYPE}/)
endif()

# Glob for sources
file(GLOB imgui_sources LIST_DIRECTORIES false ${PROJECT_SOURCE_DIR}/imgui/*.cpp ${PROJECT_SOURCE_DIR}/imgui/*.h)

# Include back-end specific sources
set(imgui_sources ${imgui_sources}
${PROJECT_SOURCE_DIR}/imgui/backends/imgui_impl_sdl.h
${PROJECT_SOURCE_DIR}/imgui/backends/imgui_impl_sdl.cpp
${PROJECT_SOURCE_DIR}/imgui/backends/imgui_impl_opengl3.cpp
${PROJECT_SOURCE_DIR}/imgui/backends/imgui_impl_opengl3.h
${PROJECT_SOURCE_DIR}/imgui/backends/imgui_impl_opengl3_loader.h
)
if(MSVC)
set(imgui_sources ${imgui_sources}
${PROJECT_SOURCE_DIR}/imgui/backends/imgui_impl_win32.h
${PROJECT_SOURCE_DIR}/imgui/backends/imgui_impl_win32.cpp
)
endif()

# Depends on the cpp and header files in case of changes
add_library(imgui STATIC ${imgui_sources})


# Specify the include directory, to be forwared to targets which link against the imgui target.
# Mark this as SYSTEM INTERFACE, so that it does not result in compiler warnings being generated for dependent projects.
# For our use case, this is up a folder so we can use imgui/imgui.h as the include, by resolving the relative path to get an abs path
get_filename_component(imgui_inc_dir ${imgui_SOURCE_DIR} REALPATH)
target_include_directories("${PROJECT_NAME}" SYSTEM INTERFACE ${imgui_SOURCE_DIR}/imgui ${imgui_SOURCE_DIR})

# Because we're using a nested dir rather than root, specify include path
target_include_directories("${PROJECT_NAME}" PUBLIC ${imgui_SOURCE_DIR}/imgui)
# Also depends on SDL for backend
target_include_directories("${PROJECT_NAME}" SYSTEM PRIVATE ${SDL2_INCLUDE_DIRS})

# Add some compile time definitions
target_compile_definitions(imgui INTERFACE $<$<CONFIG:Debug>:IMGUI_DEBUG>)
set_target_properties(imgui PROPERTIES
COMPILE_DEFINITIONS "IMGUI_EXPORT"
)

# Pic is sensible for any library
set_property(TARGET imgui PROPERTY POSITION_INDEPENDENT_CODE ON)

# Suppress warnigns from this target. (Not currently required)
#include(${CMAKE_CURRENT_LIST_DIR}/../warnings.cmake)
#DisableCompilerWarnings(TARGET imgui)

# Create an alias target for imgui to namespace it / make it more like other modern cmake
add_library(ImGui::ImGui ALIAS imgui)
39 changes: 39 additions & 0 deletions cmake/dependencies/imgui.cmake
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
#########
# imgui #
#########

set(CMAKE_MODULE_PATH ${CMAKE_CURRENT_LIST_DIR}/modules/ ${CMAKE_MODULE_PATH})
include(FetchContent)

cmake_policy(SET CMP0079 NEW)

# Change the source_dir to allow inclusion via imgui/imgui.h rather than imgui.h
FetchContent_Declare(
imgui
GIT_REPOSITORY https://github.com/ocornut/imgui.git
GIT_TAG v1.88
GIT_SHALLOW 1
SOURCE_DIR ${FETCHCONTENT_BASE_DIR}/imgui-src/imgui
GIT_PROGRESS ON
# UPDATE_DISCONNECTED ON
)

# imgui does not include anything

# @todo - try finding the package first, assuming it sets system correctly when used.
FetchContent_GetProperties(imgui)
if(NOT imgui_POPULATED)
FetchContent_Populate(imgui)

# The imgui repository does not include a CMakeLists.txt (or similar)
# Create our own cmake target for imgui.

# If the target does not already exist, add it.
# @todo - make this far more robust.
if(NOT TARGET imgui)
# make a dynamically generated CMakeLists.txt which can be add_subdirectory'd instead, so that the .vcxproj goes in a folder. Just adding a project doesn't work.
configure_file(${CMAKE_CURRENT_LIST_DIR}/imgui-CMakeLists.txt.in ${FETCHCONTENT_BASE_DIR}/imgui-src/CMakeLists.txt @ONLY)
# We've now created CMakeLists.txt so we can use MakeAvailable
add_subdirectory(${imgui_SOURCE_DIR}/.. ${imgui_BINARY_DIR})
endif()
endif()
9 changes: 9 additions & 0 deletions include/flamegpu/visualiser/FLAMEGPU_Visualisation.h
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

#include <map>
#include <string>
#include <cstdint>

#include "flamegpu/visualiser/config/TexBufferConfig.h"

Expand Down Expand Up @@ -35,11 +36,19 @@ class FLAMEGPU_Visualisation {
const std::map<TexBufferConfig::Function, TexBufferConfig>& core_tex_buffers, const std::multimap<TexBufferConfig::Function, CustomTexBufferConfig>& tex_buffers) {
updateAgentStateBuffer(agent_name.c_str(), state_name.c_str(), buffLen, core_tex_buffers, tex_buffers);
}
/**
* Provide an environment property, so it can be displayed
*/
void registerEnvironmentProperty(const std::string& property_name, void* ptr, std::type_index type, unsigned int elements, bool is_const);
/**
* Update the UI step counter
* @note When this value is first set non-0, the visualiser assumes sim has begun executing
*/
void setStepCount(const unsigned int stepCount);
/**
* Provide the random seed, so it can be displayed in the debug menu
*/
void setRandomSeed(uint64_t randomSeed);
Robadob marked this conversation as resolved.
Show resolved Hide resolved
/*
* Start visualiser in background thread
*/
Expand Down
263 changes: 263 additions & 0 deletions include/flamegpu/visualiser/config/ImGuiWidgets.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,263 @@
#ifndef INCLUDE_FLAMEGPU_VISUALISER_CONFIG_IMGUIWIDGETS_H_
#define INCLUDE_FLAMEGPU_VISUALISER_CONFIG_IMGUIWIDGETS_H_

#include <imgui.h>

#include <memory>
#include <string>
#include <cstdint>
#include <algorithm> // Not actually required, linter just thinks initialiser for max is fn call

namespace flamegpu {
namespace visualiser {

/**
* Template for converting a type to the corresponding ImGui type enum
* Useful when summing unknown values
* e.g. sum_input_t<float>::result_t == double
* e.g. sum_input_t<uint8_t>::result_t == uint64_t
*/
template <typename T> struct imgui_type;
/**
* @see imgui_type
*/
template <> struct imgui_type<char> { static constexpr ImGuiDataType_ t = ImGuiDataType_S8; static constexpr const char* fmt = "%c"; };
template <> struct imgui_type<int8_t> { static constexpr ImGuiDataType_ t = ImGuiDataType_S8; static constexpr const char* fmt = "%hhd"; };
template <> struct imgui_type<uint8_t> { static constexpr ImGuiDataType_ t = ImGuiDataType_U8; static constexpr const char* fmt = "%hhu"; };
template <> struct imgui_type<int16_t> { static constexpr ImGuiDataType_ t = ImGuiDataType_S16; static constexpr const char* fmt = "%hd"; };
template <> struct imgui_type<uint16_t> { static constexpr ImGuiDataType_ t = ImGuiDataType_U16; static constexpr const char* fmt = "%hu"; };
template <> struct imgui_type<int32_t> { static constexpr ImGuiDataType_ t = ImGuiDataType_S32; static constexpr const char* fmt = "%d"; };
template <> struct imgui_type<uint32_t> { static constexpr ImGuiDataType_ t = ImGuiDataType_U32; static constexpr const char* fmt = "%u"; };
template <> struct imgui_type<int64_t> { static constexpr ImGuiDataType_ t = ImGuiDataType_S64; static constexpr const char* fmt = "%lld"; };
template <> struct imgui_type<uint64_t> { static constexpr ImGuiDataType_ t = ImGuiDataType_U64; static constexpr const char* fmt = "%llu"; };
template <> struct imgui_type<float> { static constexpr ImGuiDataType_ t = ImGuiDataType_Float; static constexpr const char* fmt = "%g"; };
template <> struct imgui_type<double> { static constexpr ImGuiDataType_ t = ImGuiDataType_Double; static constexpr const char* fmt = "%g"; };

/**
* The most generic abstract ImGui element superclass
* All other elements inherit from this
*/
struct PanelElement {
virtual ~PanelElement() = default;
/**
* Triggers the underlying call to ImGui
*/
virtual bool addToImGui() = 0;
/**
* Provides copy construction behaviour
*/
virtual std::unique_ptr<PanelElement> clone() const = 0;
};
/**
* Separator element, creates a horizontal line between elements
*/
struct SeparatorElement : PanelElement {
/**
* Constructor
*/
SeparatorElement() { }
bool addToImGui() override { ImGui::Separator(); return false; }
[[nodiscard]] std::unique_ptr<PanelElement> clone() const override { return std::unique_ptr<PanelElement>(new SeparatorElement()); }
};
/**
* Merely denotes that return value should be treated differently
*/
struct SectionElement : PanelElement { };
/**
* Collapsible header element, creates a titled section which can be collapsed
*/
struct HeaderElement : SectionElement {
/**
* Constructor
* @param _text Text displayed in the section header
* @param _begin_open If true, the section will not begin in a collapsed state
*/
explicit HeaderElement(const std::string& _text, const bool _begin_open)
: text(_text)
, begin_open(_begin_open) { }
bool addToImGui() override { return ImGui::CollapsingHeader(text.c_str(), begin_open ? ImGuiTreeNodeFlags_DefaultOpen : 0); }
[[nodiscard]] std::unique_ptr<PanelElement> clone() const override { return std::unique_ptr<PanelElement>(new HeaderElement(text, begin_open)); }
std::string text;
bool begin_open;
};
/**
* Ends a section, unhiding following elements
*/
struct EndSectionElement : SectionElement {
/**
* Constructor
*/
EndSectionElement() { }
bool addToImGui() override { return true; }
[[nodiscard]] std::unique_ptr<PanelElement> clone() const override { return std::unique_ptr<EndSectionElement>(new EndSectionElement()); }
std::string text;
};
/**
* Label element, only contains a string used to hold it's text to be displayed
*/
struct LabelElement : PanelElement {
/**
* Constructor
* @param _text Text displayed on the label
*/
explicit LabelElement(const std::string &_text)
: text(_text) { }
bool addToImGui() override { ImGui::Text("%s", text.c_str()); return false; }
[[nodiscard]] std::unique_ptr<PanelElement> clone() const override { return std::unique_ptr<PanelElement>(new LabelElement(text)); }
std::string text;
};
/**
* Environment property modifier generic
* All environment properties inherit from this
*/
struct EnvPropertyElement : PanelElement {
/**
* Constructor
* @param _name Name of the environment property
* @param _index Index of the environment property element (0 if the property is not an array property)
* @param _ptr_offset Pointer offset from the environment property origin (as this depending on the index and type size, 0 if the property is not an array property)
* @note _ptr_offset should be calculated using the type information available to the subclass
*/
explicit EnvPropertyElement(const std::string& _name, unsigned int _index, unsigned int _ptr_offset)
: data_ptr(nullptr)
, name(_name)
, index(_index)
, ptr_offset(_ptr_offset) { }
void* data_ptr;
std::string name;
unsigned int index;
unsigned int ptr_offset;
bool is_const = false;
bool addToImGui() override = 0;
std::unique_ptr<PanelElement> clone() const override = 0;
const std::string& getName() const { return name; }
void setPtr(void *ptr) { data_ptr = static_cast<char*>(ptr) + ptr_offset; }
void setConst(bool _is_const) { is_const = _is_const; }
void setArray() { name += "[" + std::to_string(index) +"]"; }
};
/**
* ImGui slider element for an environment property
*/
template<typename T>
struct EnvPropertySlider : EnvPropertyElement {
/**
* Constructor
* @param _name Name of the environment property
* @param _index Index of the environment property element (0 if the property is not an array property)
* @param _min Minimum value of the slider
* @param _max Maximum value of the slider
*/
EnvPropertySlider(const std::string& _name, unsigned int _index, T _min, T _max)
: EnvPropertyElement(_name, _index, _index * sizeof(T))
, min(_min)
, max(_max) { }
T min, max;
bool addToImGui() override {
if (this->data_ptr) {
if (this->is_const) ImGui::BeginDisabled();
const bool rtn = ImGui::SliderScalar(this->name.c_str(), imgui_type<T>::t, this->data_ptr, &this->min, &this->max, imgui_type<T>::fmt, ImGuiSliderFlags_AlwaysClamp);
if (this->is_const) ImGui::EndDisabled();
return rtn;
}
ImGui::Text("Loading...%s", this->name.c_str());
return false;
}
[[nodiscard]] std::unique_ptr<PanelElement> clone() const override { return std::unique_ptr<PanelElement>(new EnvPropertySlider<T>(this->name, this->index, this->min, this->max)); }
};
/**
* ImGui drag element for an environment property
*/
template<typename T>
struct EnvPropertyDrag : EnvPropertyElement {
/**
* Constructor
* @param _name Name of the environment property
* @param _index Index of the environment property element (0 if the property is not an array property)
* @param _min Minimum value that can be set
* @param _max Maximum value that can be set
* @param _speed Amount the value changes per pixel dragged
*/
EnvPropertyDrag(const std::string& _name, unsigned int _index, T _min, T _max, float _speed)
: EnvPropertyElement(_name, _index, _index * sizeof(T))
, min(_min)
, max(_max)
, speed(_speed) { }
T min, max;
float speed;
bool addToImGui() override {
if (this->data_ptr) {
if (this->is_const) ImGui::BeginDisabled();
const bool rtn = ImGui::DragScalar(this->name.c_str(), imgui_type<T>::t, this->data_ptr, this->speed, &this->min, &this->max, imgui_type<T>::fmt, ImGuiSliderFlags_AlwaysClamp);
if (this->is_const) ImGui::EndDisabled();
return rtn;
}
ImGui::Text("Loading...%s", this->name.c_str());
return false;
}
[[nodiscard]] std::unique_ptr<PanelElement> clone() const override { return std::unique_ptr<PanelElement>(new EnvPropertyDrag<T>(this->name, this->index, this->min, this->max, this->speed)); }
};
/**
* ImGui input with +/- step buttons for an environment property
*/
template<typename T>
struct EnvPropertyInput : EnvPropertyElement {
/**
* Constructor
* @param _name Name of the environment property
* @param _index Index of the environment property element (0 if the property is not an array property)
* @param _step Change per button click
* @param _step_fast Change per tick when holding button (?)
*/
EnvPropertyInput(const std::string& _name, unsigned int _index, T _step, T _step_fast)
: EnvPropertyElement(_name, _index, _index * sizeof(T))
, step(_step)
, step_fast(_step_fast) { }
T step, step_fast;
bool addToImGui() override {
if (this->data_ptr) {
if (this->is_const) ImGui::BeginDisabled();
const bool rtn = ImGui::InputScalar(this->name.c_str(), imgui_type<T>::t, this->data_ptr, &this->step, &this->step_fast, imgui_type<T>::fmt, ImGuiInputTextFlags_CallbackCompletion);
if (this->is_const) ImGui::EndDisabled();
return rtn;
}
ImGui::Text("Loading...%s", this->name.c_str());
return false;
}
[[nodiscard]] std::unique_ptr<PanelElement> clone() const override { return std::unique_ptr<PanelElement>(new EnvPropertyInput<T>(this->name, this->index, this->step, this->step_fast)); }
};
/**
* ImGui checkbox for integer type environment properties
*/
template<typename T>
struct EnvPropertyToggle : EnvPropertyElement {
/**
* Constructor
* @param _name Name of the environment property
* @param _index Index of the environment property element (0 if the property is not an array property)
*/
explicit EnvPropertyToggle(const std::string& _name, unsigned int _index)
: EnvPropertyElement(_name, _index, _index * sizeof(T))
, data(false) { }
bool data;
bool addToImGui() override {
if (this->data_ptr) {
if (is_const) ImGui::BeginDisabled();
// How to sync data
if (ImGui::Checkbox(this->name.c_str(), &data)) {
*static_cast<T*>(data_ptr) = static_cast<T>(data ? 1 : 0);
return true;
}
// Really not sure that this sync will work as desired if the model updates the value independently
data = *static_cast<T*>(data_ptr);
if (is_const) ImGui::EndDisabled();
return false;
}
ImGui::Text("Loading...%s", this->name.c_str());
return false;
}
[[nodiscard]] std::unique_ptr<PanelElement> clone() const override { return std::unique_ptr<PanelElement>(new EnvPropertyToggle<T>(this->name, this->index)); }
};
} // namespace visualiser
} // namespace flamegpu

#endif // INCLUDE_FLAMEGPU_VISUALISER_CONFIG_IMGUIWIDGETS_H_
Loading