- popup buttons: moved arguments into a struct and added size parameter for the button in addition to the popup
- added functions for rendering clipped text/text with ellipsis - data table: added special rendering for tables with "uniform" rows that skips the drawing of invisible items - added InputText() function that takes an std::array as buffer - added SizedSeparator() functions that work similar to ImGui::Separator() but allow you to set its size
This commit is contained in:
@@ -9,6 +9,7 @@ if not hasattr(env, 'Jinja'):
|
||||
src_files = Split("""
|
||||
application.cpp
|
||||
config.cpp
|
||||
imraid.cpp
|
||||
imutil.cpp
|
||||
fonts.gen.cpp
|
||||
stb_image.cpp
|
||||
|
||||
33
private/raid/imraid.cpp
Normal file
33
private/raid/imraid.cpp
Normal file
@@ -0,0 +1,33 @@
|
||||
|
||||
#include "raid/imraid.hpp"
|
||||
|
||||
namespace ImRaid
|
||||
{
|
||||
void RenderTextClippedEx(ImDrawList* draw_list, const ImVec2& pos_min, const ImVec2& pos_max, const char* text, const char* text_display_end, const ImVec2* text_size_if_known, const ImVec2& align, const ImRect* clip_rect)
|
||||
{
|
||||
// Perform CPU side clipping for single clipped element to avoid using scissor state
|
||||
ImVec2 pos = pos_min;
|
||||
const ImVec2 text_size = text_size_if_known ? *text_size_if_known : ImGui::CalcTextSize(text, text_display_end, false, 0.0f);
|
||||
|
||||
const ImVec2* clip_min = clip_rect ? &clip_rect->Min : &pos_min;
|
||||
const ImVec2* clip_max = clip_rect ? &clip_rect->Max : &pos_max;
|
||||
bool need_clipping = (pos.x + text_size.x >= clip_max->x) || (pos.y + text_size.y >= clip_max->y);
|
||||
if (clip_rect) // If we had no explicit clipping rectangle then pos==clip_min
|
||||
need_clipping |= (pos.x < clip_min->x) || (pos.y < clip_min->y);
|
||||
|
||||
// Align whole block. We should defer that to the better rendering function when we'll have support for individual line alignment.
|
||||
if (align.x > 0.0f) pos.x = pos.x + ((pos_max.x - pos.x - text_size.x) * align.x);
|
||||
if (align.y > 0.0f) pos.y = pos.y + ((pos_max.y - pos.y - text_size.y) * align.y);
|
||||
|
||||
// Render
|
||||
if (need_clipping)
|
||||
{
|
||||
ImVec4 fine_clip_rect(clip_min->x, clip_min->y, clip_max->x, clip_max->y);
|
||||
draw_list->AddText(NULL, 0.0f, pos, ImGui::GetColorU32(ImGuiCol_Text), text, text_display_end, 0.0f, &fine_clip_rect);
|
||||
}
|
||||
else
|
||||
{
|
||||
draw_list->AddText(NULL, 0.0f, pos, ImGui::GetColorU32(ImGuiCol_Text), text, text_display_end, 0.0f, NULL);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -110,32 +110,39 @@ inline bool ToggleImageButton(const char* strId, ImTextureID textureId, const Im
|
||||
return clicked;
|
||||
}
|
||||
|
||||
struct PopupButtonArgs
|
||||
{
|
||||
ImVec2 buttonSize = {};
|
||||
ImVec2 popupSize = {};
|
||||
ImGuiWindowFlags popupFlags = ImGuiWindowFlags_NoNav | ImGuiWindowFlags_NoSavedSettings;
|
||||
};
|
||||
|
||||
template<bool small>
|
||||
inline bool BeginPopupButtonImpl(const char* label, const ImVec2& size = {} , ImGuiWindowFlags flags = ImGuiWindowFlags_NoNav | ImGuiWindowFlags_NoSavedSettings)
|
||||
inline bool BeginPopupButtonImpl(const char* label, const PopupButtonArgs& args = {})
|
||||
{
|
||||
char popupId[128] = {"popup##"};
|
||||
std::strcat(popupId, label);
|
||||
|
||||
const float popupX = ImGui::GetCursorScreenPos().x;
|
||||
const bool open = small ? ImGui::SmallButton(label) : ImGui::Button(label);
|
||||
const bool open = small ? ImGui::SmallButton(label) : ImGui::Button(label, args.buttonSize);
|
||||
if (open) {
|
||||
ImGui::OpenPopup(popupId);
|
||||
}
|
||||
|
||||
const float popupY = ImGui::GetCursorScreenPos().y;
|
||||
ImGui::SetNextWindowPos({popupX, popupY});
|
||||
ImGui::SetNextWindowSize(size);
|
||||
return ImGui::BeginPopup(popupId, flags);
|
||||
ImGui::SetNextWindowSize(args.popupSize);
|
||||
return ImGui::BeginPopup(popupId, args.popupFlags);
|
||||
}
|
||||
|
||||
inline bool BeginPopupButton(const char* label, const ImVec2& size = {} , ImGuiWindowFlags flags = ImGuiWindowFlags_NoNav | ImGuiWindowFlags_NoSavedSettings)
|
||||
inline bool BeginPopupButton(const char* label, const PopupButtonArgs& args = {})
|
||||
{
|
||||
return BeginPopupButtonImpl<false>(label, size, flags);
|
||||
return BeginPopupButtonImpl<false>(label, args);
|
||||
}
|
||||
|
||||
inline bool BeginSmallPopupButton(const char* label, const ImVec2& size = {} , ImGuiWindowFlags flags = ImGuiWindowFlags_NoNav | ImGuiWindowFlags_NoSavedSettings)
|
||||
inline bool BeginSmallPopupButton(const char* label, const PopupButtonArgs& args = {})
|
||||
{
|
||||
return BeginPopupButtonImpl<true>(label, size, flags);
|
||||
return BeginPopupButtonImpl<true>(label, args);
|
||||
}
|
||||
|
||||
inline void TextUnformatted(std::string_view stringView)
|
||||
@@ -158,6 +165,43 @@ inline void TextWithBackground(std::string_view stringView, const ImU32 color, f
|
||||
TextUnformatted(stringView);
|
||||
}
|
||||
|
||||
void RenderTextClippedEx(ImDrawList* draw_list, const ImVec2& pos_min, const ImVec2& pos_max, const char* text, const char* text_display_end, const ImVec2* text_size_if_known = nullptr, const ImVec2& align = ImVec2(0.f, 0.f), const ImRect* clip_rect = nullptr);
|
||||
|
||||
template<bool ellipsisLeft = false, typename... TArgs>
|
||||
void TextEllipsis(float maxWidth, std::format_string<TArgs...> fmt, TArgs&&... args)
|
||||
{
|
||||
const raid::TempText formatted = raid::formatTemp(fmt, std::forward<TArgs>(args)...);
|
||||
const ImVec2 textSize = ImGui::CalcTextSize(formatted, formatted.end());
|
||||
|
||||
if (textSize.x > maxWidth)
|
||||
{
|
||||
static constexpr const char* kEllipsis = "...";
|
||||
const float ellipsisWidth = ImGui::CalcTextSize(kEllipsis, nullptr).x;
|
||||
const ImVec2 cursorPos = ImGui::GetCursorScreenPos();
|
||||
if constexpr (!ellipsisLeft)
|
||||
{
|
||||
const ImVec2 posMin = cursorPos;
|
||||
const ImVec2 posMax = cursorPos + ImVec2(maxWidth - ellipsisWidth, textSize.y);
|
||||
RenderTextClippedEx(ImGui::GetWindowDrawList(), posMin, posMax, formatted, formatted.end(), &textSize);
|
||||
ImGui::RenderText(posMin + ImVec2(maxWidth - ellipsisWidth, 0.f), kEllipsis);
|
||||
}
|
||||
else
|
||||
{
|
||||
ImGui::RenderText(cursorPos, kEllipsis);
|
||||
|
||||
const ImVec2 posMin = cursorPos + ImVec2(ellipsisWidth, 0.f);
|
||||
const ImVec2 posMax = posMin + ImVec2(maxWidth - ellipsisWidth, textSize.y);
|
||||
RenderTextClippedEx(ImGui::GetWindowDrawList(), posMin, posMax, formatted, formatted.end(), &textSize, /* align = */ ImVec2(1.f, 0.f));
|
||||
}
|
||||
|
||||
ImGui::SetCursorScreenPos(cursorPos + ImVec2(maxWidth + ImGui::GetStyle().ItemSpacing.x, 0.f));
|
||||
ImGui::NewLine();
|
||||
}
|
||||
else {
|
||||
ImGui::TextUnformatted(formatted, formatted.end());
|
||||
}
|
||||
}
|
||||
|
||||
inline float CalcButtonWidth(const char* text)
|
||||
{
|
||||
return ImGui::CalcTextSize(text).x + (2.f * ImGui::GetStyle().FramePadding.x);
|
||||
@@ -203,15 +247,15 @@ struct DataTableState
|
||||
bool dirty = true;
|
||||
};
|
||||
|
||||
template<typename TObject>
|
||||
using object_arg_t = std::conditional_t<std::is_class_v<TObject>, const TObject&, TObject>;
|
||||
|
||||
template<typename TObject>
|
||||
struct DataTableColumn
|
||||
{
|
||||
struct CellRendererArgs
|
||||
{
|
||||
const TObject& object;
|
||||
};
|
||||
using renderer_t = std::function<void(const CellRendererArgs&)>;
|
||||
using comparator_t = std::function<bool(const TObject&, const TObject&)>;
|
||||
using arg_t = object_arg_t<TObject>;
|
||||
using renderer_t = std::function<void(arg_t)>;
|
||||
using comparator_t = std::function<bool(arg_t, arg_t)>;
|
||||
|
||||
const char* header;
|
||||
renderer_t renderer;
|
||||
@@ -248,16 +292,23 @@ struct DataTableTreeItem
|
||||
ImGuiTreeNodeFlags flags = ImGuiTreeNodeFlags_Leaf | ImGuiTreeNodeFlags_SpanAllColumns;
|
||||
};
|
||||
|
||||
struct DataTableRowInfo
|
||||
{
|
||||
bool visible = false;
|
||||
};
|
||||
|
||||
template<typename TObject>
|
||||
struct DataTableOptions
|
||||
{
|
||||
|
||||
using filter_t = std::function<bool(const TObject&)>;
|
||||
using getid_t = std::function<const char*(const TObject&)>;
|
||||
using click_handler_t = std::function<void(const TObject&)>;
|
||||
using getstate_t = std::function<DataTableItemState(const TObject&)>;
|
||||
using select_handler_t = std::function<void(const TObject&, SelectAction)>;
|
||||
using gettreeitem_t = std::function<DataTableTreeItem(const TObject&)>;
|
||||
using arg_t = object_arg_t<TObject>;
|
||||
using filter_t = std::function<bool(arg_t)>;
|
||||
using getid_t = std::function<const char*(arg_t)>;
|
||||
using click_handler_t = std::function<void(arg_t)>;
|
||||
using hover_handler_t = std::function<void(arg_t)>;
|
||||
using getstate_t = std::function<DataTableItemState(arg_t)>;
|
||||
using select_handler_t = std::function<void(arg_t, SelectAction)>;
|
||||
using gettreeitem_t = std::function<DataTableTreeItem(arg_t)>;
|
||||
using beginrow_t = std::function<void(arg_t, const DataTableRowInfo&)>;
|
||||
|
||||
std::span<const DataTableColumn<TObject>> columns;
|
||||
ImGuiTableFlags tableFlags = ImGuiTableFlags_Resizable | ImGuiTableFlags_Reorderable | ImGuiTableFlags_Sortable
|
||||
@@ -265,18 +316,23 @@ struct DataTableOptions
|
||||
bool hoverable = false;
|
||||
bool tree = false;
|
||||
bool noHeaders = false;
|
||||
bool nonUniformRows = false;
|
||||
|
||||
filter_t filter = {};
|
||||
getid_t getId = {};
|
||||
click_handler_t rightClickHandler = {};
|
||||
hover_handler_t hoverHandler = {};
|
||||
getstate_t getState = {};
|
||||
select_handler_t selectHandler = {};
|
||||
gettreeitem_t getTreeItem = {};
|
||||
beginrow_t beginRow = {};
|
||||
};
|
||||
|
||||
template<typename TObject, typename TData>
|
||||
inline void DataTable(const char* strId, const DataTableOptions<TObject>& options, TData&& data, DataTableState& state, DataTableRenderState<TObject>& renderState, ImVec2 outerSize = {})
|
||||
{
|
||||
using arg_t = typename DataTableOptions<TObject>::arg_t;
|
||||
|
||||
if (outerSize.y <= 0.f) {
|
||||
outerSize.y = ImGui::GetContentRegionAvail().y;
|
||||
}
|
||||
@@ -333,7 +389,7 @@ inline void DataTable(const char* strId, const DataTableOptions<TObject>& option
|
||||
char getIdBuffer[kGetIdBufferSize] = {0};
|
||||
if (!getId)
|
||||
{
|
||||
getId = [&](const TObject& object)
|
||||
getId = [&](arg_t object)
|
||||
{
|
||||
std::snprintf(getIdBuffer, kGetIdBufferSize, "##%016" PRIXPTR, reinterpret_cast<std::uintptr_t>(&object));
|
||||
return getIdBuffer;
|
||||
@@ -341,9 +397,9 @@ inline void DataTable(const char* strId, const DataTableOptions<TObject>& option
|
||||
}
|
||||
|
||||
typename DataTableOptions<TObject>::gettreeitem_t getTreeItem = options.getTreeItem;
|
||||
if (!getTreeItem)
|
||||
if (options.tree && !getTreeItem)
|
||||
{
|
||||
getTreeItem = [](const TObject&) -> DataTableTreeItem {
|
||||
getTreeItem = [](arg_t) -> DataTableTreeItem {
|
||||
return {};
|
||||
};
|
||||
}
|
||||
@@ -351,24 +407,19 @@ inline void DataTable(const char* strId, const DataTableOptions<TObject>& option
|
||||
renderState.rowIdx = 0;
|
||||
renderState.currentTreeDepth = 0;
|
||||
|
||||
auto renderRow = [&](const TObject& object)
|
||||
auto renderRow = [&](arg_t object)
|
||||
{
|
||||
if (options.tree && getTreeItem(object).depth > renderState.currentTreeDepth) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (options.filter && !options.filter(object)) {
|
||||
return;
|
||||
}
|
||||
|
||||
ImGui::TableNextRow();
|
||||
renderState.currentObject = &object;
|
||||
|
||||
bool hoverNext = options.hoverable || options.tree;
|
||||
bool hoverNext = options.hoverable || options.tree; // should the next (first) column be "hovered" (depends on the table type)
|
||||
renderState.item = options.getState ? options.getState(object) : DataTableItemState{};
|
||||
|
||||
for (const DataTableColumn<TObject>& column : options.columns)
|
||||
{
|
||||
ImGui::TableNextColumn();
|
||||
if (!ImGui::TableNextColumn()) {
|
||||
continue; // TODO: does options.nonUniformRows apply here? -> TableNextColumn() appears to return true, even if the column is completely clipped
|
||||
}
|
||||
|
||||
if (hoverNext)
|
||||
{
|
||||
hoverNext = false;
|
||||
@@ -409,15 +460,145 @@ inline void DataTable(const char* strId, const DataTableOptions<TObject>& option
|
||||
if (options.rightClickHandler && ImGui::IsItemClicked(ImGuiMouseButton_Right)) {
|
||||
options.rightClickHandler(object);
|
||||
}
|
||||
if (options.hoverHandler && ImGui::IsItemHovered()) {
|
||||
options.hoverHandler(object);
|
||||
}
|
||||
ImGui::SameLine();
|
||||
}
|
||||
column.renderer({
|
||||
.object = object
|
||||
});
|
||||
column.renderer(object);
|
||||
}
|
||||
|
||||
++renderState.rowIdx;
|
||||
};
|
||||
|
||||
auto itemHidden = [&](arg_t object) -> bool
|
||||
{
|
||||
// when rendering a tree, skip any non-expanded items
|
||||
if (options.tree && getTreeItem(object).depth > renderState.currentTreeDepth) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// if there is a filter, apply it
|
||||
if (options.filter && !options.filter(object)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
auto renderRowsUniform = [&](auto&& range)
|
||||
{
|
||||
auto it = range.begin();
|
||||
auto end = range.end();
|
||||
|
||||
// find the first non-hidden row
|
||||
for (; it != end; ++it)
|
||||
{
|
||||
if (!itemHidden(*it)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// nothing to render? fine
|
||||
if (it == end) {
|
||||
return;
|
||||
}
|
||||
|
||||
// time to render the first row and measure its height
|
||||
ImGui::TableNextRow();
|
||||
const float startY = ImGui::GetCursorScreenPos().y;
|
||||
renderRow(*it);
|
||||
++it;
|
||||
|
||||
// just one row? also fine
|
||||
if (it == end) {
|
||||
return;
|
||||
}
|
||||
|
||||
// jump to the next row and measure the distance
|
||||
ImGui::TableNextRow();
|
||||
const float rowHeight = ImGui::GetCursorScreenPos().y - startY;
|
||||
renderRow(*it);
|
||||
++it;
|
||||
|
||||
// skip over items until we reach the clip rect
|
||||
const float clipStartY = ImGui::GetWindowDrawList()->GetClipRectMin().y;
|
||||
const float clippedY = clipStartY - ImGui::GetCursorScreenPos().y;
|
||||
if (clippedY >= rowHeight) // only if at least one item could be skipped
|
||||
{
|
||||
// number of items that can be skipped
|
||||
const unsigned itemsToSkip = static_cast<unsigned>(clippedY / rowHeight);
|
||||
|
||||
// count the number of items we'll actually skip
|
||||
unsigned skippedItems = 0;
|
||||
for (; it != end && skippedItems < itemsToSkip; ++it)
|
||||
{
|
||||
if (!itemHidden(*it)) {
|
||||
++skippedItems;
|
||||
}
|
||||
}
|
||||
|
||||
if (skippedItems == 0)
|
||||
{
|
||||
// nothing to skip means everything was hidden -> we can stop here
|
||||
return;
|
||||
}
|
||||
|
||||
// create a fake row with the height of <skippedItems> regular rows
|
||||
const float skipHeight = static_cast<float>(skippedItems) * rowHeight;
|
||||
ImGui::TableNextRow(ImGuiTableRowFlags_None, skipHeight);
|
||||
}
|
||||
|
||||
// calculate the number of rows within the clip region that can actually be rendered
|
||||
const float clipEndY = ImGui::GetWindowDrawList()->GetClipRectMax().y;
|
||||
const unsigned visibleRows = static_cast<unsigned>((clipEndY - clipStartY) / rowHeight) + 2; // always +2 for the (partially occluded) items at the top and bottom
|
||||
|
||||
// finally we can actually render the rows
|
||||
unsigned rowsRendered = 0;
|
||||
for (; it != end && rowsRendered < visibleRows; ++it)
|
||||
{
|
||||
if (itemHidden(*it)) {
|
||||
continue;
|
||||
}
|
||||
ImGui::TableNextRow(ImGuiTableRowFlags_None, rowHeight);
|
||||
renderRow(*it);
|
||||
++rowsRendered;
|
||||
}
|
||||
|
||||
// finally, count the number of rows that could have been rendered, but are outside the clip region
|
||||
unsigned itemsRemaining = 0;
|
||||
for (; it != end; ++it)
|
||||
{
|
||||
if (!itemHidden(*it)) {
|
||||
++itemsRemaining;
|
||||
}
|
||||
}
|
||||
|
||||
// anything remaining? -> do another fake row
|
||||
if (itemsRemaining > 0)
|
||||
{
|
||||
const float skipHeight = static_cast<float>(itemsRemaining) * rowHeight;
|
||||
ImGui::TableNextRow(ImGuiTableRowFlags_None, skipHeight);
|
||||
}
|
||||
};
|
||||
|
||||
auto renderRowsNonUniform = [&](auto&& range)
|
||||
{
|
||||
auto it = range.begin();
|
||||
auto end = range.end();
|
||||
|
||||
// if rows are non-uniform, all of them need to be properly rendered to determine the table height
|
||||
// simpler, but a lot less effective
|
||||
|
||||
for (; it != end; ++it)
|
||||
{
|
||||
if (!itemHidden(*it))
|
||||
{
|
||||
ImGui::TableNextRow();
|
||||
renderRow(*it);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
if constexpr (kSortable)
|
||||
{
|
||||
@@ -436,12 +617,12 @@ inline void DataTable(const char* strId, const DataTableOptions<TObject>& option
|
||||
{
|
||||
for (const DataTableState::SortColumn& column : state.sortColumns)
|
||||
{
|
||||
const bool less = options.columns[column.index].comparator(data[leftIdx], data[rightIdx]);
|
||||
const bool less = options.columns[column.index].comparator(data.at(leftIdx), data.at(rightIdx));
|
||||
if (less)
|
||||
{ // left < right
|
||||
return !column.sortDescending;
|
||||
}
|
||||
if (options.columns[column.index].comparator(data[rightIdx], data[leftIdx]))
|
||||
if (options.columns[column.index].comparator(data.at(rightIdx), data.at(leftIdx)))
|
||||
{ // left > right
|
||||
return column.sortDescending;
|
||||
}
|
||||
@@ -451,15 +632,24 @@ inline void DataTable(const char* strId, const DataTableOptions<TObject>& option
|
||||
});
|
||||
state.dirty = false;
|
||||
}
|
||||
|
||||
for (const std::size_t dataIdx : state.sortedIndices) {
|
||||
renderRow(std::forward<TData>(data)[dataIdx]);
|
||||
|
||||
auto range = std::views::transform(state.sortedIndices, [&](std::size_t idx) -> decltype(auto) {
|
||||
return data.at(idx);
|
||||
});
|
||||
if (!options.nonUniformRows && !options.tree) { // TODO: make this work with trees
|
||||
renderRowsUniform(range);
|
||||
}
|
||||
else {
|
||||
renderRowsNonUniform(range);
|
||||
}
|
||||
}
|
||||
else // constexpr kSortable
|
||||
{
|
||||
for (const TObject& object : std::forward<TData>(data)) {
|
||||
renderRow(object);
|
||||
if (!options.nonUniformRows && !options.tree) { // TODO: make this work with trees
|
||||
renderRowsUniform(data);
|
||||
}
|
||||
else {
|
||||
renderRowsNonUniform(data);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -479,30 +669,33 @@ void DataTable(const char* strId, const DataTableOptions<TObject>& options, TDat
|
||||
template<typename TObject>
|
||||
DataTableColumn<TObject> MakeStringColumn(const char* header, const char* TObject::* member)
|
||||
{
|
||||
using arg_t = typename DataTableColumn<TObject>::arg_t;
|
||||
return {
|
||||
.header = header,
|
||||
.renderer = [member](const auto& args) { ImGui::TextUnformatted(args.object.*member); },
|
||||
.comparator = [member](const TObject& left, const TObject& right) { return std::string_view(left.*member) < std::string_view(right.*member); }
|
||||
.renderer = [member](arg_t object) { ImGui::TextUnformatted(object.*member); },
|
||||
.comparator = [member](arg_t left, arg_t right) { return std::string_view(left.*member) < std::string_view(right.*member); }
|
||||
};
|
||||
}
|
||||
|
||||
template<typename TObject>
|
||||
DataTableColumn<TObject> MakeStringColumn(const char* header, std::string TObject::* member)
|
||||
{
|
||||
using arg_t = typename DataTableColumn<TObject>::arg_t;
|
||||
return {
|
||||
.header = header,
|
||||
.renderer = [member](const auto& args) { ImGui::TextUnformatted((args.object.*member).c_str()); },
|
||||
.comparator = [member](const TObject& left, const TObject& right) { return left.*member < right.*member; }
|
||||
.renderer = [member](arg_t object) { ImGui::TextUnformatted((object.*member).c_str()); },
|
||||
.comparator = [member](arg_t left, arg_t right) { return left.*member < right.*member; }
|
||||
};
|
||||
}
|
||||
|
||||
template<typename TObject, typename TMember>
|
||||
DataTableColumn<TObject> MakeColumn(const char* header, const char* fmt, TMember TObject::* member)
|
||||
{
|
||||
using arg_t = typename DataTableColumn<TObject>::arg_t;
|
||||
return {
|
||||
.header = header,
|
||||
.renderer = [fmt, member](const auto& args) { ImGui::Text(fmt, args.object.*member); },
|
||||
.comparator = [member](const TObject& left, const TObject& right) { return left.*member < right.*member; }
|
||||
.renderer = [fmt, member](arg_t object) { ImGui::Text(fmt, object.*member); },
|
||||
.comparator = [member](arg_t left, arg_t right) { return left.*member < right.*member; }
|
||||
};
|
||||
}
|
||||
|
||||
@@ -907,6 +1100,28 @@ void InputScalar(const char* label, T* value, T step = T(0), T stepFast = T(0),
|
||||
{
|
||||
ImGui::InputScalar(label, raid::kImGuiDataType<T>, value, &step, &stepFast, format, flags);
|
||||
}
|
||||
|
||||
template<std::size_t N>
|
||||
void InputText(const char* label, std::array<char, N>& buffer, ImGuiInputTextFlags flags = 0, ImGuiInputTextCallback callback = nullptr, void* userdata = nullptr)
|
||||
{
|
||||
ImGui::InputText(label, buffer.data(), buffer.size(), flags, callback, userdata);
|
||||
}
|
||||
|
||||
inline void SizedSeparator(ImVec2 size)
|
||||
{
|
||||
const ImVec2 cursorPos = ImGui::GetCursorScreenPos();
|
||||
const ImRect rect = {cursorPos, cursorPos + size};
|
||||
|
||||
ImGui::ItemSize(size);
|
||||
if (ImGui::ItemAdd(rect, 0)) {
|
||||
ImGui::GetWindowDrawList()->AddRectFilled(rect.Min, rect.Max, ImGui::GetColorU32(ImGuiCol_Separator));
|
||||
}
|
||||
}
|
||||
|
||||
inline void SizedSeparator(float width)
|
||||
{
|
||||
SizedSeparator({width, 1.f});
|
||||
}
|
||||
} // namespace ImRaid
|
||||
|
||||
#endif // !defined(RAID_PUBLIC_RAID_IMRAID_HPP_INCLUDED)
|
||||
|
||||
Reference in New Issue
Block a user