23 float alphaMult = 1.0f) -> ImU32
25 return ImGui::ColorConvertFloat4ToU32({1.0f, 1.0f, 1.0f, alphaMult});
30 float alphaMult) -> ImU32
32 return ImGui::ColorConvertFloat4ToU32({element.
element.value.x,
35 (element.
element.value.w) * alphaMult});
39 void draw_gradient_background(ImDrawList* drawList,
47 ImGui::RenderFrame(barOriginPos, barOriginPos +
size, ImGui::GetColorU32(ImGuiCol_FrameBg),
true, ImGui::GetStyle().FrameRounding);
52 void draw_gradient_background<math::color>(ImDrawList* drawList,
60 const float gridStep =
size.y / 2.0f;
62 ImGui::RenderColorRectWithAlphaCheckerboard(drawList,
65 IM_COL32(50, 50, 50, 128), gridStep, ImVec2(0, 0));
71 void draw_gradient_combined_element(ImDrawList* drawList,
73 const T& default_value,
74 const std::vector<float>& xkeys,
75 const std::vector<size_t>& ind,
83 inline void draw_gradient_combined_element<math::color>(ImDrawList* drawList,
86 const std::vector<float>& xkeys,
87 const std::vector<size_t>& ind,
88 ImVec2 barOriginPos, ImVec2
size)
93 auto c1 = deault_value;
97 c1 = gradient.
sample(xkeys[i]);
99 const uint32_t colorAU32 = c1;
101 static constexpr auto rounding{1.f};
102 drawList->AddRectFilled(ImVec2(barOriginPos.x + xkeys[i] *
size.x, barOriginPos.y),
103 ImVec2(barOriginPos.x + xkeys[i + 1] *
size.x, barOriginPos.y +
size.y),
107 else if(ind.size() == 2)
110 auto c1 = deault_value;
111 auto c2 = deault_value;
115 c1 = gradient.
sample(xkeys[ind[0]]);
116 c2 = gradient.
sample(xkeys[ind[1]]);
119 const uint32_t colorAU32 = c1;
120 const uint32_t colorBU32 = c2;
122 drawList->AddRectFilledMultiColor(
123 ImVec2(barOriginPos.x + xkeys[ind[0]] *
size.x, barOriginPos.y),
124 ImVec2(barOriginPos.x + xkeys[ind[1]] *
size.x, barOriginPos.y +
size.y),
135 void draw_gradient_combined_elements(ImDrawList* drawList,
137 const T& default_value,
138 const std::vector<float>& xkeys,
144 for(
size_t i = 0; i < xkeys.size() - 1; i++)
146 std::vector<size_t> ind = {i, i+1};
147 draw_gradient_combined_element<T>(drawList,
159 for(
size_t i = 0; i < xkeys.size() - 1; i++)
161 std::vector<size_t> ind = {i};
163 draw_gradient_combined_element<T>(drawList,
176 inline void draw_gradient_combined_elements<float>(ImDrawList* drawList,
178 const float& deault_value,
179 const std::vector<float>& xkeys,
180 ImVec2 barOriginPos, ImVec2
size)
182 ImGui::SetCursorScreenPos(barOriginPos);
184 size_t sample_count = std::max(
size_t(64), xkeys.size());
185 std::vector<float> ykeys;
186 ykeys.reserve(sample_count);
187 float min_y = std::numeric_limits<float>::max();
188 float max_y = std::numeric_limits<float>::min();
190 for(
size_t i = 0; i < sample_count; i++)
192 float progress =
static_cast<float>(i) /
static_cast<float>(sample_count - 1);
193 float y = gradient.
sample(progress);
194 ykeys.emplace_back(
y);
195 min_y = std::min(min_y,
y);
196 max_y = std::max(max_y,
y);
199 ImGui::PlotLines(
"##", ykeys.data(), ykeys.size(), 0,
"", min_y, max_y, ImVec2(
size.x,
size.y));
204 inline void draw_gradient_combined_elements<frange_t>(ImDrawList* drawList,
207 const std::vector<float>& xkeys,
208 ImVec2 barOriginPos, ImVec2
size)
210 ImGui::SetCursorScreenPos(barOriginPos);
212 size_t sample_count = std::max(
size_t(64), xkeys.size());
231 std::vector<ImGui::ImRange> ykeys;
232 ykeys.reserve(sample_count);
233 float min_y = std::numeric_limits<float>::max();
234 float max_y = std::numeric_limits<float>::min();
236 for(
size_t i = 0; i < sample_count; i++)
238 float progress =
static_cast<float>(i) /
static_cast<float>(sample_count - 1);
240 ykeys.emplace_back(ImGui::ImRange{ry.
min, ry.
max});
241 min_y = std::min(min_y, ry.
min);
242 max_y = std::max(max_y, ry.
max);
245 ImGui::PlotEx(ImGuiPlotType_Histogram,
"##", [](
void* data,
int idx) -> ImGui::ImRange {
246 auto& ykeys = *
static_cast<std::vector<ImGui::ImRange>*
>(data);
248 }, &ykeys, ykeys.size(), 0,
"", min_y, max_y, ImVec2(
size.x,
size.y));
254 const std::function<
bool(T&)>& edit_element,
255 const T& default_value)
257 struct TemporaryState
260 int selectedIndex = -1;
261 int draggingIndex = -1;
264 enum class DrawMarkerMode
271 auto DrawMarker = [](
const ImVec2& pmin,
const ImVec2& pmax,
const ImU32& color, DrawMarkerMode mode)
273 auto drawList = ImGui::GetWindowDrawList();
274 const auto w =
static_cast<int32_t
>(pmax.x - pmin.x);
275 const auto h =
static_cast<int32_t
>(pmax.y - pmin.y);
276 const auto sign = std::signbit(
static_cast<float>(h)) ? -1 : 1;
278 const auto margin = 2;
279 const auto marginh = margin * sign;
281 if(mode != DrawMarkerMode::None)
283 const auto outlineColor = mode == DrawMarkerMode::Selected
284 ? ImGui::ColorConvertFloat4ToU32({0.0f, 0.0f, 1.0f, 1.0f})
285 : ImGui::ColorConvertFloat4ToU32({0.2f, 0.2f, 0.2f, 1.0f});
287 drawList->AddTriangleFilled({pmin.x + w / 2, pmin.y},
288 {pmin.x + 0, pmin.y + h / 2},
289 {pmin.x + w, pmin.y + h / 2},
292 drawList->AddRectFilled({pmin.x + 0, pmin.y + h / 2}, {pmin.x + w, pmin.y + h}, outlineColor);
295 drawList->AddTriangleFilled({pmin.x + w / 2, pmin.y + marginh},
296 {pmin.x + 0 + margin, pmin.y + h / 2},
297 {pmin.x + w - margin, pmin.y + h / 2},
300 drawList->AddRectFilled({pmin.x + 0 + margin, pmin.y + h / 2}, {pmin.x + w - margin, pmin.y + h - marginh}, color);
305 int32_t& selectedIndex, int32_t& draggingIndex)
313 std::vector<SortedMarker> sortedMarker;
315 for(
size_t i = 0; i <
a.size(); i++)
317 sortedMarker.emplace_back(SortedMarker{i,
a[i]});
320 std::sort(sortedMarker.begin(),
322 [](
const SortedMarker&
a,
const SortedMarker&
b)
324 return a.marker < b.marker;
327 for(
size_t i = 0; i <
a.size(); i++)
329 a[i] = sortedMarker[i].marker;
332 if(selectedIndex != -1)
334 for(
size_t i = 0; i <
a.size(); i++)
336 if(selectedIndex >= 0 && sortedMarker[i].index ==
size_t(selectedIndex))
344 if(draggingIndex != -1)
346 for(
size_t i = 0; i <
a.size(); i++)
348 if(draggingIndex >= 0 && sortedMarker[i].index ==
size_t(draggingIndex))
359 enum class MarkerDirection
365 struct UpdateMarkerResult
371 auto UpdateMarker = [&](TemporaryState& temporaryState,
378 MarkerDirection markerDir) -> UpdateMarkerResult
380 UpdateMarkerResult ret;
381 ret.isChanged =
false;
382 ret.isHovered =
false;
384 float markerOffset = markerWidth * 0.5f;
385 for(
size_t i = 0; i < points.size(); i++)
387 const auto x = (int)(points[i].progress * width);
390 if(temporaryState.selectedIndex >= 0 &&
size_t(temporaryState.selectedIndex) == i)
392 mode = DrawMarkerMode::Selected;
396 mode = DrawMarkerMode::Unselected;
399 if(markerDir == MarkerDirection::ToLower)
401 DrawMarker({originPos.x +
x - markerOffset, originPos.y + markerHeight},
402 {originPos.x +
x + markerOffset, originPos.y + 0},
403 get_gradient_element_color<T>(points[i]),
408 DrawMarker({originPos.x +
x - markerOffset, originPos.y + 0},
409 {originPos.x +
x + markerOffset, originPos.y + markerHeight},
410 get_gradient_element_color<T>(points[i]),
414 float pick_padding = 4.0f;
415 ImGui::SetCursorScreenPos({originPos.x +
x - (markerOffset + pick_padding), originPos.y});
417 ImGui::InvisibleButton((keyStr + std::to_string(i)).c_str(), {markerWidth + pick_padding * 2.0f, markerHeight + pick_padding * 2.0f});
419 ret.isHovered |= ImGui::IsItemHovered();
421 if(temporaryState.draggingIndex == -1 && ImGui::IsItemHovered() && ImGui::IsMouseDown(ImGuiMouseButton_Left))
423 temporaryState.selectedIndex = i;
424 temporaryState.draggingIndex = i;
427 if(!ImGui::IsMouseDown(ImGuiMouseButton_Left))
429 temporaryState.draggingIndex = -1;
432 if(temporaryState.draggingIndex >= 0 &&
size_t(temporaryState.draggingIndex) == i && ImGui::IsMouseDragging(ImGuiMouseButton_Left, 6.0f))
434 const auto diff = ImGui::GetIO().MouseDelta.x / width;
435 points[i].progress += diff;
436 points[i].progress = std::max(std::min(points[i].progress, 1.0f), 0.0f);
438 ret.isChanged |= diff != 0.0f;
446 bool changed =
false;
448 static TemporaryState temporaryState{};
449 auto widgetId = ImGui::GetID(title.c_str());
450 ImGui::PushID(widgetId);
451 TemporaryState tempState{};
452 tempState.activeId = widgetId;
454 if(widgetId == temporaryState.activeId)
456 tempState = temporaryState;
459 auto drawList = ImGui::GetWindowDrawList();
461 const float width = int(ImGui::CalcItemWidth());
462 const auto barHeight = ImGui::GetFrameHeight() * 1.5f;
463 const auto markerWidth = 16;
464 const auto markerHeight = 16;
466 const auto barOriginPos = ImGui::GetCursorScreenPos();
467 draw_gradient_background(drawList, gradient, barOriginPos, {width, barHeight});
470 std::vector<float> xkeys;
474 for(
size_t i = 0; i < points.size(); i++)
476 xkeys.emplace_back(points[i].progress);
479 xkeys.emplace_back(0.0f);
480 xkeys.emplace_back(1.0f);
482 std::sort(xkeys.begin(), xkeys.end());
483 auto result = std::unique(xkeys.begin(), xkeys.end());
484 xkeys.erase(result, xkeys.end());
487 draw_gradient_combined_elements<T>(drawList,
492 ImVec2(width, barHeight));
497 auto originPosBelowBar = ImGui::GetCursorScreenPos();
500 const auto resultColor = UpdateMarker(tempState,
507 MarkerDirection::ToUpper);
509 changed |= resultColor.isChanged;
511 if(tempState.draggingIndex != -1)
513 SortMarkers(points, tempState.selectedIndex, tempState.draggingIndex);
517 if(resultColor.isChanged)
523 ImGui::SetCursorScreenPos(barOriginPos);
525 ImGui::InvisibleButton(
"MarkerArea", {width,
static_cast<float>(markerHeight * 1.5f + barHeight)});
527 if(ImGui::IsItemHovered())
529 const float x = (ImGui::GetIO().MousePos.x - (barOriginPos.x));
530 const float xn =
x / width;
539 if(!resultColor.isHovered)
541 auto c = get_gradient_element_color<T>(element, 0.5f);
542 DrawMarker({originPosBelowBar.x +
x - markerWidth * 0.5f, originPosBelowBar.y + 0},
543 {originPosBelowBar.x +
x + markerWidth * 0.5f, originPosBelowBar.y + markerHeight},
545 DrawMarkerMode::None);
548 if(ImGui::IsMouseClicked(ImGuiMouseButton_Left))
551 changed |= index >= 0;
552 tempState.selectedIndex = index;
555 ImGui::SameLine(0.0f, ImGui::GetStyle().ItemInnerSpacing.x);
561 ImGui::SetItemTooltipEx(
"%s",
"Clear the gradient's elements");
564 int indexToRemove = -1;
566 for(
size_t i = 0; i < points.size(); ++i)
575 ImGui::SameLine(0.0f, ImGui::GetStyle().ItemInnerSpacing.x);
577 changed |= edit_element(points[i].element);
580 if(tempState.selectedIndex >= 0 && i ==
size_t(tempState.selectedIndex))
582 ImGui::SetItemFocusFrame();
594 if(indexToRemove != -1)
601 if(tempState.selectedIndex != -1)
603 temporaryState = tempState;
611 inline void draw_title(
const std::string& title)
617 ImGui::TextUnformatted(title.c_str());
623 ImGui::PushID(
"InterpolationMode");
625 std::vector<std::string> interpolation_modes = {
"Linear",
"Constant"};
627 bool changed =
false;
629 if(ImGui::BeginCombo(
"##", interpolation_modes[interpolation_index].c_str()))
631 for(
size_t i = 0; i < interpolation_modes.size(); i++)
633 if(ImGui::Selectable(interpolation_modes[i].c_str()))
635 interpolation_index = i;
643 ImGui::SameLine(0.0f, ImGui::GetStyle().ItemInnerSpacing.x);
644 ImGui::TextUnformatted(
"Interpolation");
656 const std::function<
bool(T&)>& edit_element,
657 const T& default_value)
659 bool changed =
false;
661 ImGui::PushID(title.c_str());
663 changed |= draw_interpolation_mode(gradient);
664 changed |= draw_gradient_impl<T>(title, gradient, edit_element, default_value);
1186 entt::meta_any& var,
1199 auto rotation = data.get_rotation();
1200 auto scale = data.get_scale();
1201 auto skew = data.get_skew();
1204 auto type = entt::resolve<math::transform>();
1206 static math::vec3 euler_angles(0.0f, 0.0f, 0.0f);
1208 math::quat old_quat(math::radians(euler_angles));
1210 float dot_product = math::dot(old_quat, rotation);
1211 bool equal = (dot_product > (1.0f - math::epsilon<float>()));
1212 if(!equal && (!ImGui::IsMouseDragging(ImGuiMouseButton_Left) || ImGuizmo::IsUsing()))
1214 euler_angles = data.get_rotation_euler_degrees();
1219 ImGui::PushID(
"Position");
1221 auto prop =
type.data(
"position"_hs);
1226 override_ctx.
push_segment(prop_name, prop_pretty_name);
1229 layout.
set_data(prop_pretty_name,
"");
1236 ImGui::GetContentRegionAvail().
x,
1237 ImGui::GetFrameHeight(),
1242 data.reset_position();
1243 result.changed =
true;
1244 result.edit_finished =
true;
1249 result.edit_finished |= ImGui::IsItemDeactivatedAfterEdit();
1250 ImGui::SetItemTooltipEx(
"Reset %s", prop_pretty_name.c_str());
1254 ImGui::PushItemWidth(ImGui::GetContentRegionAvail().
x);
1256 static const auto reset = math::zero<math::vec3>();
1257 if(DragVec3(position, info, &reset))
1259 data.set_position(position);
1260 result.changed |=
true;
1265 result.edit_finished |= ImGui::IsItemDeactivatedAfterEdit();
1267 ImGui::PopItemWidth();
1269 override_ctx.pop_segment();
1273 ImGui::PushID(
"Rotation");
1275 auto prop =
type.data(
"rotation"_hs);
1280 override_ctx.
push_segment(prop_name, prop_pretty_name);
1283 layout.
set_data(prop_pretty_name,
"");
1290 ImGui::GetContentRegionAvail().
x,
1291 ImGui::GetFrameHeight(),
1296 data.reset_rotation();
1297 result.changed =
true;
1298 result.edit_finished =
true;
1301 add_property_action(ctx, override_ctx, result, prop_proxy, rotation, data.get_rotation(), prop.custom());
1303 result.edit_finished |= ImGui::IsItemDeactivatedAfterEdit();
1305 ImGui::SetItemTooltipEx(
"Reset %s", prop_pretty_name.c_str());
1309 ImGui::PushItemWidth(ImGui::GetContentRegionAvail().
x);
1311 auto old_euler = euler_angles;
1312 static const auto reset = math::zero<math::vec3>();
1313 if(DragVec3(euler_angles, info, &reset,
"%.2f°"))
1315 data.rotate_local(math::radians(euler_angles - old_euler));
1316 result.changed |=
true;
1319 add_property_action(ctx, override_ctx, result, prop_proxy, rotation, data.get_rotation(), prop.custom());
1321 result.edit_finished |= ImGui::IsItemDeactivatedAfterEdit();
1324 ImGui::PopItemWidth();
1326 override_ctx.pop_segment();
1330 ImGui::PushID(
"Scale");
1332 auto prop =
type.data(
"scale"_hs);
1337 override_ctx.
push_segment(prop_name, prop_pretty_name);
1340 layout.
set_data(prop_pretty_name,
"");
1344 static bool locked_scale =
false;
1348 ImGui::GetContentRegionAvail().
x,
1349 ImGui::CalcItemSize(label).
x + ImGui::GetFrameHeight() + ImGui::GetStyle().ItemSpacing.x,
1352 if(ImGui::Button(label))
1354 locked_scale = !locked_scale;
1357 ImGui::SetItemTooltipEx(
"Enable/Disable Constrained Proportions");
1364 result.changed =
true;
1365 result.edit_finished =
true;
1370 result.edit_finished |= ImGui::IsItemDeactivatedAfterEdit();
1372 ImGui::SetItemTooltipEx(
"Reset %s", prop_pretty_name.c_str());
1375 layout.prepare_for_item();
1377 ImGui::PushItemWidth(ImGui::GetContentRegionAvail().
x);
1379 static const auto reset = math::one<math::vec3>();
1380 auto before_scale =
scale;
1381 if(DragVec3(
scale, info, &reset))
1383 auto delta =
scale - before_scale;
1387 before_scale += math::vec3(delta.x);
1388 before_scale += math::vec3(delta.y);
1389 before_scale += math::vec3(delta.z);
1390 scale = before_scale;
1393 data.set_scale(
scale);
1394 result.changed |=
true;
1400 result.edit_finished |= ImGui::IsItemDeactivatedAfterEdit();
1402 ImGui::PopItemWidth();
1404 override_ctx.pop_segment();
1408 ImGui::PushID(
"Skew");
1410 auto prop =
type.data(
"skew"_hs);
1415 override_ctx.
push_segment(prop_name, prop_pretty_name);
1418 layout.
set_data(prop_pretty_name,
"");
1425 ImGui::GetContentRegionAvail().
x,
1426 ImGui::GetFrameHeight(),
1432 result.changed =
true;
1433 result.edit_finished =
true;
1436 add_property_action(ctx, override_ctx, result, prop_proxy, skew, data.get_skew(), prop.custom());
1438 result.edit_finished |= ImGui::IsItemDeactivatedAfterEdit();
1439 ImGui::SetItemTooltipEx(
"Reset %s", prop_pretty_name.c_str());
1443 ImGui::PushItemWidth(ImGui::GetContentRegionAvail().
x);
1445 static const auto reset = math::zero<math::vec3>();
1446 if(DragVec3(skew, info, &reset))
1448 data.set_skew(skew);
1449 result.changed |=
true;
1451 add_property_action(ctx, override_ctx, result, prop_proxy, skew, data.get_skew(), prop.custom());
1454 result.edit_finished |= ImGui::IsItemDeactivatedAfterEdit();
1456 ImGui::PopItemWidth();
1458 override_ctx.pop_segment();