diff --git a/examples/example_hud/gui/main_window.cpp b/examples/example_hud/gui/main_window.cpp index 5534f09f..6813cd11 100644 --- a/examples/example_hud/gui/main_window.cpp +++ b/examples/example_hud/gui/main_window.cpp @@ -68,71 +68,81 @@ namespace imgui_desktop::gui if (ImGui::CollapsingHeader("Entity", ImGuiTreeNodeFlags_DefaultOpen)) { - ImGui::SliderFloat("X##ent", &m_entity_x, 100.f, vp->Size.x - 100.f); - ImGui::SliderFloat("Top Y", &m_entity_top_y, 20.f, m_entity_bottom_y - 20.f); - ImGui::SliderFloat("Bottom Y", &m_entity_bottom_y, m_entity_top_y + 20.f, vp->Size.y - 20.f); + ImGui::SliderFloat("X##ent", &m_entity_x, 100.f, vp->Size.x - 100.f); + ImGui::SliderFloat("Top Y", &m_entity_top_y, 20.f, m_entity_bottom_y - 20.f); + ImGui::SliderFloat("Bottom Y", &m_entity_bottom_y, m_entity_top_y + 20.f, vp->Size.y - 20.f); } if (ImGui::CollapsingHeader("Box", ImGuiTreeNodeFlags_DefaultOpen)) { - ImGui::Checkbox("Box", &m_show_box); ImGui::SameLine(); - ImGui::Checkbox("Cornered", &m_show_cornered_box); ImGui::SameLine(); - ImGui::Checkbox("Dashed", &m_show_dashed_box); + ImGui::Checkbox("Box##chk", &m_show_box); + ImGui::SameLine(); + ImGui::Checkbox("Cornered", &m_show_cornered_box); + ImGui::SameLine(); + ImGui::Checkbox("Dashed", &m_show_dashed_box); ImGui::ColorEdit4("Color##box", reinterpret_cast(&m_box_color), ImGuiColorEditFlags_NoInputs); - ImGui::ColorEdit4("Fill##box", reinterpret_cast(&m_box_fill), ImGuiColorEditFlags_NoInputs); - ImGui::SliderFloat("Thickness", &m_box_thickness, 0.5f, 5.f); - ImGui::SliderFloat("Corner ratio", &m_corner_ratio, 0.05f, 0.5f); + ImGui::ColorEdit4("Fill##box", reinterpret_cast(&m_box_fill), ImGuiColorEditFlags_NoInputs); + ImGui::SliderFloat("Thickness", &m_box_thickness, 0.5f, 5.f); + ImGui::SliderFloat("Corner ratio", &m_corner_ratio, 0.05f, 0.5f); ImGui::Separator(); - ImGui::ColorEdit4("Dash color", reinterpret_cast(&m_dash_color), ImGuiColorEditFlags_NoInputs); - ImGui::SliderFloat("Dash length", &m_dash_len, 2.f, 30.f); - ImGui::SliderFloat("Dash gap", &m_dash_gap, 1.f, 20.f); - ImGui::SliderFloat("Dash thick", &m_dash_thickness, 0.5f, 5.f); + ImGui::ColorEdit4("Dash color", reinterpret_cast(&m_dash_color), ImGuiColorEditFlags_NoInputs); + ImGui::SliderFloat("Dash length", &m_dash_len, 2.f, 30.f); + ImGui::SliderFloat("Dash gap", &m_dash_gap, 1.f, 20.f); + ImGui::SliderFloat("Dash thick", &m_dash_thickness, 0.5f, 5.f); } if (ImGui::CollapsingHeader("Bars", ImGuiTreeNodeFlags_DefaultOpen)) { - ImGui::ColorEdit4("Color##bar", reinterpret_cast(&m_bar_color), ImGuiColorEditFlags_NoInputs); - ImGui::ColorEdit4("BG##bar", reinterpret_cast(&m_bar_bg_color), ImGuiColorEditFlags_NoInputs); - ImGui::ColorEdit4("Outline##bar", reinterpret_cast(&m_bar_outline_color), ImGuiColorEditFlags_NoInputs); - ImGui::SliderFloat("Width##bar", &m_bar_width, 1.f, 20.f); - ImGui::SliderFloat("Value##bar", &m_bar_value, 0.f, 1.f); + ImGui::ColorEdit4("Color##bar", reinterpret_cast(&m_bar_color), ImGuiColorEditFlags_NoInputs); + ImGui::ColorEdit4("BG##bar", reinterpret_cast(&m_bar_bg_color), ImGuiColorEditFlags_NoInputs); + ImGui::ColorEdit4("Outline##bar", reinterpret_cast(&m_bar_outline_color), + ImGuiColorEditFlags_NoInputs); + ImGui::SliderFloat("Width##bar", &m_bar_width, 1.f, 20.f); + ImGui::SliderFloat("Value##bar", &m_bar_value, 0.f, 1.f); ImGui::SliderFloat("Offset##bar", &m_bar_offset, 1.f, 20.f); - ImGui::Checkbox("Right##bar", &m_show_right_bar); ImGui::SameLine(); - ImGui::Checkbox("Left##bar", &m_show_left_bar); - ImGui::Checkbox("Top##bar", &m_show_top_bar); ImGui::SameLine(); - ImGui::Checkbox("Bottom##bar", &m_show_bottom_bar); - ImGui::Checkbox("Right dashed##bar", &m_show_right_dashed_bar); ImGui::SameLine(); - ImGui::Checkbox("Left dashed##bar", &m_show_left_dashed_bar); - ImGui::Checkbox("Top dashed##bar", &m_show_top_dashed_bar); ImGui::SameLine(); - ImGui::Checkbox("Bot dashed##bar", &m_show_bottom_dashed_bar); - ImGui::SliderFloat("Dash len##bar", &m_bar_dash_len, 2.f, 20.f); - ImGui::SliderFloat("Dash gap##bar", &m_bar_dash_gap, 1.f, 15.f); + ImGui::Checkbox("Right##bar", &m_show_right_bar); + ImGui::SameLine(); + ImGui::Checkbox("Left##bar", &m_show_left_bar); + ImGui::Checkbox("Top##bar", &m_show_top_bar); + ImGui::SameLine(); + ImGui::Checkbox("Bottom##bar", &m_show_bottom_bar); + ImGui::Checkbox("Right dashed##bar", &m_show_right_dashed_bar); + ImGui::SameLine(); + ImGui::Checkbox("Left dashed##bar", &m_show_left_dashed_bar); + ImGui::Checkbox("Top dashed##bar", &m_show_top_dashed_bar); + ImGui::SameLine(); + ImGui::Checkbox("Bot dashed##bar", &m_show_bottom_dashed_bar); + ImGui::SliderFloat("Dash len##bar", &m_bar_dash_len, 2.f, 20.f); + ImGui::SliderFloat("Dash gap##bar", &m_bar_dash_gap, 1.f, 15.f); } if (ImGui::CollapsingHeader("Labels", ImGuiTreeNodeFlags_DefaultOpen)) { ImGui::Checkbox("Outlined", &m_outlined); ImGui::SliderFloat("Offset##lbl", &m_label_offset, 0.f, 15.f); - ImGui::Checkbox("Right##lbl", &m_show_right_labels); ImGui::SameLine(); - ImGui::Checkbox("Left##lbl", &m_show_left_labels); - ImGui::Checkbox("Top##lbl", &m_show_top_labels); ImGui::SameLine(); - ImGui::Checkbox("Bottom##lbl", &m_show_bottom_labels); - ImGui::Checkbox("Ctr top##lbl", &m_show_centered_top); ImGui::SameLine(); - ImGui::Checkbox("Ctr bot##lbl", &m_show_centered_bottom); + ImGui::Checkbox("Right##lbl", &m_show_right_labels); + ImGui::SameLine(); + ImGui::Checkbox("Left##lbl", &m_show_left_labels); + ImGui::Checkbox("Top##lbl", &m_show_top_labels); + ImGui::SameLine(); + ImGui::Checkbox("Bottom##lbl", &m_show_bottom_labels); + ImGui::Checkbox("Ctr top##lbl", &m_show_centered_top); + ImGui::SameLine(); + ImGui::Checkbox("Ctr bot##lbl", &m_show_centered_bottom); } if (ImGui::CollapsingHeader("Skeleton")) { - ImGui::Checkbox("Show##skel", &m_show_skeleton); - ImGui::ColorEdit4("Color##skel", reinterpret_cast(&m_skel_color), ImGuiColorEditFlags_NoInputs); - ImGui::SliderFloat("Thick##skel", &m_skel_thickness, 0.5f, 5.f); + ImGui::Checkbox("Show##skel", &m_show_skeleton); + ImGui::ColorEdit4("Color##skel", reinterpret_cast(&m_skel_color), ImGuiColorEditFlags_NoInputs); + ImGui::SliderFloat("Thick##skel", &m_skel_thickness, 0.5f, 5.f); } if (ImGui::CollapsingHeader("Snap Line")) { - ImGui::Checkbox("Show##snap", &m_show_snap); - ImGui::ColorEdit4("Color##snap", reinterpret_cast(&m_snap_color), ImGuiColorEditFlags_NoInputs); - ImGui::SliderFloat("Width##snap", &m_snap_width, 0.5f, 5.f); + ImGui::Checkbox("Show##snap", &m_show_snap); + ImGui::ColorEdit4("Color##snap", reinterpret_cast(&m_snap_color), ImGuiColorEditFlags_NoInputs); + ImGui::SliderFloat("Width##snap", &m_snap_width, 0.5f, 5.f); } ImGui::PopItemWidth(); @@ -141,77 +151,64 @@ namespace imgui_desktop::gui void MainWindow::draw_overlay() { + using namespace omath::hud::widget; + using omath::hud::when; const auto* vp = ImGui::GetMainViewport(); - - omath::hud::EntityOverlay ent( - {m_entity_x, m_entity_top_y}, {m_entity_x, m_entity_bottom_y}, - std::make_shared()); - - draw_boxes(ent); - draw_bars(ent); - draw_labels(ent); - - if (m_show_skeleton) - ent.add_skeleton(m_skel_color, m_skel_thickness); - if (m_show_snap) - ent.add_snap_line({vp->Size.x / 2.f, vp->Size.y}, m_snap_color, m_snap_width); - } - - void MainWindow::draw_boxes(omath::hud::EntityOverlay& ent) const - { - if (m_show_box) - ent.add_2d_box(m_box_color, m_box_fill, m_box_thickness); - if (m_show_cornered_box) - ent.add_cornered_2d_box(omath::Color::from_rgba(255, 0, 255, 255), m_box_fill, m_corner_ratio, m_box_thickness); - if (m_show_dashed_box) - ent.add_dashed_box(m_dash_color, m_dash_len, m_dash_gap, m_dash_thickness); - } - - void MainWindow::draw_bars(omath::hud::EntityOverlay& ent) const - { - if (m_show_right_bar) - ent.add_right_bar(m_bar_color, m_bar_outline_color, m_bar_bg_color, m_bar_width, m_bar_value, m_bar_offset); - if (m_show_left_bar) - ent.add_left_bar(m_bar_color, m_bar_outline_color, m_bar_bg_color, m_bar_width, m_bar_value, m_bar_offset); - if (m_show_top_bar) - ent.add_top_bar(m_bar_color, m_bar_outline_color, m_bar_bg_color, m_bar_width, m_bar_value, m_bar_offset); - if (m_show_bottom_bar) - ent.add_bottom_bar(m_bar_color, m_bar_outline_color, m_bar_bg_color, m_bar_width, m_bar_value, m_bar_offset); - - if (m_show_right_dashed_bar) - ent.add_right_dashed_bar(m_bar_color, m_bar_outline_color, m_bar_bg_color, m_bar_width, m_bar_value, m_bar_dash_len, m_bar_dash_gap, m_bar_offset); - if (m_show_left_dashed_bar) - ent.add_left_dashed_bar(m_bar_color, m_bar_outline_color, m_bar_bg_color, m_bar_width, m_bar_value, m_bar_dash_len, m_bar_dash_gap, m_bar_offset); - if (m_show_top_dashed_bar) - ent.add_top_dashed_bar(m_bar_color, m_bar_outline_color, m_bar_bg_color, m_bar_width, m_bar_value, m_bar_dash_len, m_bar_dash_gap, m_bar_offset); - if (m_show_bottom_dashed_bar) - ent.add_bottom_dashed_bar(m_bar_color, m_bar_outline_color, m_bar_bg_color, m_bar_width, m_bar_value, m_bar_dash_len, m_bar_dash_gap, m_bar_offset); - } - - void MainWindow::draw_labels(omath::hud::EntityOverlay& ent) const - { - if (m_show_right_labels) - { - ent.add_right_label({0.f, 1.f, 0.f, 1.f}, m_label_offset, m_outlined, "Health: {}/100", 100); - ent.add_right_label({1.f, 0.f, 0.f, 1.f}, m_label_offset, m_outlined, "Shield: {}/125", 125); - ent.add_right_label({1.f, 0.f, 1.f, 1.f}, m_label_offset, m_outlined, "*LOCKED*"); - } - if (m_show_left_labels) - { - ent.add_left_label(omath::Color::from_rgba(255, 128, 0, 255), m_label_offset, m_outlined, "Armor: 75"); - ent.add_left_label(omath::Color::from_rgba(0, 200, 255, 255), m_label_offset, m_outlined, "Level: 42"); - } - if (m_show_top_labels) - { - ent.add_top_label(omath::Color::from_rgba(255, 255, 0, 255), m_label_offset, m_outlined, "*SCOPED*"); - ent.add_top_label(omath::Color::from_rgba(255, 0, 0, 255), m_label_offset, m_outlined, "*BLEEDING*"); - } - if (m_show_centered_top) - ent.add_centered_top_label(omath::Color::from_rgba(0, 255, 255, 255), m_label_offset, m_outlined, "*VISIBLE*"); - if (m_show_centered_bottom) - ent.add_centered_bottom_label(omath::Color::from_rgba(255, 255, 255, 255), m_label_offset, m_outlined, "PlayerName"); - if (m_show_bottom_labels) - ent.add_bottom_label(omath::Color::from_rgba(200, 200, 0, 255), m_label_offset, m_outlined, "42m"); + const Bar bar{m_bar_color, m_bar_outline_color, m_bar_bg_color, m_bar_width, m_bar_value, m_bar_offset}; + const DashedBar dbar{m_bar_color, m_bar_outline_color, m_bar_bg_color, m_bar_width, + m_bar_value, m_bar_dash_len, m_bar_dash_gap, m_bar_offset}; + + omath::hud::EntityOverlay({m_entity_x, m_entity_top_y}, {m_entity_x, m_entity_bottom_y}, + std::make_shared()) + .contents( + // ── Boxes ──────────────────────────────────────────────────── + when(m_show_box, Box{m_box_color, m_box_fill, m_box_thickness}), + when(m_show_cornered_box, CorneredBox{omath::Color::from_rgba(255, 0, 255, 255), m_box_fill, + m_corner_ratio, m_box_thickness}), + when(m_show_dashed_box, DashedBox{m_dash_color, m_dash_len, m_dash_gap, m_dash_thickness}), + + RightSide + { + when(m_show_right_bar, bar), + when(m_show_right_dashed_bar, dbar), + when(m_show_right_labels, + Label{{0.f, 1.f, 0.f, 1.f}, m_label_offset, m_outlined, "Health: 100/100"}), + when(m_show_right_labels, + Label{{1.f, 0.f, 0.f, 1.f}, m_label_offset, m_outlined, "Shield: 125/125"}), + when(m_show_right_labels, + Label{{1.f, 0.f, 1.f, 1.f}, m_label_offset, m_outlined, "*LOCKED*"}), + }, + LeftSide + { + when(m_show_left_bar, bar), + when(m_show_left_dashed_bar, dbar), + when(m_show_left_labels, Label{omath::Color::from_rgba(255, 128, 0, 255), + m_label_offset, m_outlined, "Armor: 75"}), + when(m_show_left_labels, Label{omath::Color::from_rgba(0, 200, 255, 255), + m_label_offset, m_outlined, "Level: 42"}), + }, + TopSide + { + when(m_show_top_bar, bar), + when(m_show_top_dashed_bar, dbar), + when(m_show_centered_top, Centered{Label{omath::Color::from_rgba(0, 255, 255, 255), + m_label_offset, m_outlined, "*VISIBLE*"}}), + when(m_show_top_labels, Label{omath::Color::from_rgba(255, 255, 0, 255), m_label_offset, + m_outlined, "*SCOPED*"}), + when(m_show_top_labels, Label{omath::Color::from_rgba(255, 0, 0, 255), m_label_offset, + m_outlined, "*BLEEDING*"}), + }, + BottomSide + { + when(m_show_bottom_bar, bar), + when(m_show_bottom_dashed_bar, dbar), + when(m_show_centered_bottom, Centered{Label{omath::Color::from_rgba(255, 255, 255, 255), + m_label_offset, m_outlined, "PlayerName"}}), + when(m_show_bottom_labels, Label{omath::Color::from_rgba(200, 200, 0, 255), + m_label_offset, m_outlined, "42m"}), + }, + when(m_show_skeleton, Skeleton{m_skel_color, m_skel_thickness}), + when(m_show_snap, SnapLine{{vp->Size.x / 2.f, vp->Size.y}, m_snap_color, m_snap_width})); } void MainWindow::present() diff --git a/examples/example_hud/gui/main_window.hpp b/examples/example_hud/gui/main_window.hpp index 9a6f5676..6572bdf4 100644 --- a/examples/example_hud/gui/main_window.hpp +++ b/examples/example_hud/gui/main_window.hpp @@ -19,9 +19,6 @@ namespace imgui_desktop::gui private: void draw_controls(); void draw_overlay(); - void draw_boxes(omath::hud::EntityOverlay& ent) const; - void draw_bars(omath::hud::EntityOverlay& ent) const; - void draw_labels(omath::hud::EntityOverlay& ent) const; void present(); GLFWwindow* m_window = nullptr; @@ -46,8 +43,8 @@ namespace imgui_desktop::gui omath::Color m_bar_bg_color{0.f, 0.f, 0.f, 0.5f}; omath::Color m_bar_outline_color{0.f, 0.f, 0.f, 1.f}; float m_bar_width = 4.f, m_bar_value = 0.75f, m_bar_offset = 5.f; - bool m_show_right_bar = true, m_show_left_bar = true; - bool m_show_top_bar = true, m_show_bottom_bar = true; + bool m_show_right_bar = true, m_show_left_bar = true; + bool m_show_top_bar = true, m_show_bottom_bar = true; bool m_show_right_dashed_bar = false, m_show_left_dashed_bar = false; bool m_show_top_dashed_bar = false, m_show_bottom_dashed_bar = false; float m_bar_dash_len = 6.f, m_bar_dash_gap = 4.f; diff --git a/include/omath/hud/entity_overlay.hpp b/include/omath/hud/entity_overlay.hpp index 6e18a80d..5de3c193 100644 --- a/include/omath/hud/entity_overlay.hpp +++ b/include/omath/hud/entity_overlay.hpp @@ -3,127 +3,161 @@ // #pragma once #include "canvas_box.hpp" +#include "entity_overlay_widgets.hpp" #include "hud_renderer_interface.hpp" #include "omath/linear_algebra/vector2.hpp" #include "omath/utility/color.hpp" #include #include + namespace omath::hud { class EntityOverlay final { public: EntityOverlay(const Vector2& top, const Vector2& bottom, - const std::shared_ptr& renderer); - - void add_2d_box(const Color& box_color, const Color& fill_color = Color{0.f, 0.f, 0.f, 0.f}, - float thickness = 1.f) const; - - void add_cornered_2d_box(const Color& box_color, const Color& fill_color = Color{0.f, 0.f, 0.f, 0.f}, - float corner_ratio_len = 0.2f, float thickness = 1.f) const; + const std::shared_ptr& renderer); - void add_right_bar(const Color& color, const Color& outline_color, const Color& bg_color, float width, - float ratio, float offset = 5.f); + // ── Boxes ──────────────────────────────────────────────────────── + EntityOverlay& add_2d_box(const Color& box_color, const Color& fill_color = Color{0.f, 0.f, 0.f, 0.f}, + float thickness = 1.f); - void add_left_bar(const Color& color, const Color& outline_color, const Color& bg_color, float width, - float ratio, float offset = 5.f); + EntityOverlay& add_cornered_2d_box(const Color& box_color, const Color& fill_color = Color{0.f, 0.f, 0.f, 0.f}, + float corner_ratio_len = 0.2f, float thickness = 1.f); - template - void add_right_label(const Color& color, const float offset, const bool outlined, - std::format_string fmt, Args&&... args) - { - const std::string label = std::vformat(fmt.get(), std::make_format_args(args...)); + EntityOverlay& add_dashed_box(const Color& color, float dash_len = 8.f, float gap_len = 5.f, + float thickness = 1.f); - add_right_label(color, offset, outlined, std::string_view{label}); - } + // ── Bars ───────────────────────────────────────────────────────── + EntityOverlay& add_right_bar(const Color& color, const Color& outline_color, const Color& bg_color, float width, + float ratio, float offset = 5.f); - void add_right_label(const Color& color, float offset, bool outlined, const std::string_view& text); + EntityOverlay& add_left_bar(const Color& color, const Color& outline_color, const Color& bg_color, float width, + float ratio, float offset = 5.f); - template - void add_top_label(const Color& color, const float offset, const bool outlined, std::format_string fmt, - Args&&... args) - { - const std::string label = std::vformat(fmt.get(), std::make_format_args(args...)); + EntityOverlay& add_top_bar(const Color& color, const Color& outline_color, const Color& bg_color, float height, + float ratio, float offset = 5.f); - add_top_label(color, offset, outlined, std::string_view{label}); - } + EntityOverlay& add_bottom_bar(const Color& color, const Color& outline_color, const Color& bg_color, + float height, float ratio, float offset = 5.f); - void add_top_label(const Color& color, float offset, bool outlined, std::string_view text); + EntityOverlay& add_right_dashed_bar(const Color& color, const Color& outline_color, const Color& bg_color, + float width, float ratio, float dash_len, float gap_len, + float offset = 5.f); - void add_top_bar(const Color& color, const Color& outline_color, const Color& bg_color, float height, - float ratio, float offset = 5.f); + EntityOverlay& add_left_dashed_bar(const Color& color, const Color& outline_color, const Color& bg_color, + float width, float ratio, float dash_len, float gap_len, float offset = 5.f); - void add_snap_line(const Vector2& start_pos, const Color& color, float width); + EntityOverlay& add_top_dashed_bar(const Color& color, const Color& outline_color, const Color& bg_color, + float height, float ratio, float dash_len, float gap_len, float offset = 5.f); - void add_dashed_box(const Color& color, float dash_len = 8.f, float gap_len = 5.f, - float thickness = 1.f) const; + EntityOverlay& add_bottom_dashed_bar(const Color& color, const Color& outline_color, const Color& bg_color, + float height, float ratio, float dash_len, float gap_len, + float offset = 5.f); - void add_skeleton(const Color& color, float thickness = 1.f) const; + // ── Labels ─────────────────────────────────────────────────────── + EntityOverlay& add_right_label(const Color& color, float offset, bool outlined, const std::string_view& text); - void add_right_dashed_bar(const Color& color, const Color& outline_color, const Color& bg_color, - float width, float ratio, float dash_len, float gap_len, float offset = 5.f); + EntityOverlay& add_left_label(const Color& color, float offset, bool outlined, const std::string_view& text); - void add_left_dashed_bar(const Color& color, const Color& outline_color, const Color& bg_color, - float width, float ratio, float dash_len, float gap_len, float offset = 5.f); + EntityOverlay& add_top_label(const Color& color, float offset, bool outlined, std::string_view text); - void add_top_dashed_bar(const Color& color, const Color& outline_color, const Color& bg_color, - float height, float ratio, float dash_len, float gap_len, float offset = 5.f); + EntityOverlay& add_bottom_label(const Color& color, float offset, bool outlined, std::string_view text); - void add_bottom_dashed_bar(const Color& color, const Color& outline_color, const Color& bg_color, - float height, float ratio, float dash_len, float gap_len, float offset = 5.f); + EntityOverlay& add_centered_top_label(const Color& color, float offset, bool outlined, + const std::string_view& text); - void add_bottom_bar(const Color& color, const Color& outline_color, const Color& bg_color, float height, - float ratio, float offset = 5.f); + EntityOverlay& add_centered_bottom_label(const Color& color, float offset, bool outlined, + const std::string_view& text); template - void add_bottom_label(const Color& color, const float offset, const bool outlined, - std::format_string fmt, Args&&... args) + EntityOverlay& add_right_label(const Color& color, float offset, bool outlined, std::format_string fmt, + Args&&... args) { - const std::string label = std::vformat(fmt.get(), std::make_format_args(args...)); - add_bottom_label(color, offset, outlined, std::string_view{label}); + return add_right_label(color, offset, outlined, + std::string_view{std::vformat(fmt.get(), std::make_format_args(args...))}); } - void add_bottom_label(const Color& color, float offset, bool outlined, std::string_view text); - template - void add_left_label(const Color& color, const float offset, const bool outlined, - std::format_string fmt, Args&&... args) + EntityOverlay& add_left_label(const Color& color, float offset, bool outlined, std::format_string fmt, + Args&&... args) { - const std::string label = std::vformat(fmt.get(), std::make_format_args(args...)); - add_left_label(color, offset, outlined, std::string_view{label}); + return add_left_label(color, offset, outlined, + std::string_view{std::vformat(fmt.get(), std::make_format_args(args...))}); } - void add_left_label(const Color& color, float offset, bool outlined, const std::string_view& text); + template + EntityOverlay& add_top_label(const Color& color, float offset, bool outlined, std::format_string fmt, + Args&&... args) + { + return add_top_label(color, offset, outlined, + std::string_view{std::vformat(fmt.get(), std::make_format_args(args...))}); + } template - void add_centered_bottom_label(const Color& color, const float offset, const bool outlined, - std::format_string fmt, Args&&... args) + EntityOverlay& add_bottom_label(const Color& color, float offset, bool outlined, + std::format_string fmt, Args&&... args) { - const std::string label = std::vformat(fmt.get(), std::make_format_args(args...)); - add_centered_bottom_label(color, offset, outlined, std::string_view{label}); + return add_bottom_label(color, offset, outlined, + std::string_view{std::vformat(fmt.get(), std::make_format_args(args...))}); } - void add_centered_bottom_label(const Color& color, float offset, bool outlined, const std::string_view& text); + template + EntityOverlay& add_centered_top_label(const Color& color, float offset, bool outlined, + std::format_string fmt, Args&&... args) + { + return add_centered_top_label(color, offset, outlined, + std::string_view{std::vformat(fmt.get(), std::make_format_args(args...))}); + } template - void add_centered_top_label(const Color& color, const float offset, const bool outlined, - std::format_string fmt, Args&&... args) + EntityOverlay& add_centered_bottom_label(const Color& color, float offset, bool outlined, + std::format_string fmt, Args&&... args) { - const std::string label = std::vformat(fmt.get(), std::make_format_args(args...)); - add_centered_top_label(color, offset, outlined, std::string_view{label}); + return add_centered_bottom_label(color, offset, outlined, + std::string_view{std::vformat(fmt.get(), std::make_format_args(args...))}); } - void add_centered_top_label(const Color& color, float offset, bool outlined, const std::string_view& text); + // ── Misc ───────────────────────────────────────────────────────── + EntityOverlay& add_snap_line(const Vector2& start_pos, const Color& color, float width); + + EntityOverlay& add_skeleton(const Color& color, float thickness = 1.f); + + // ── Declarative interface ───────────────────────────────────────── + /// Pass any combination of widget:: descriptor structs (and std::optional + /// from when()) to render them all in declaration order. + template + EntityOverlay& contents(Widgets&&... widgets) + { + (dispatch(std::forward(widgets)), ...); + return *this; + } private: - void draw_outlined_text(const Vector2& position, const Color& color, - const std::string_view& text); - void draw_dashed_line(const Vector2& from, const Vector2& to, const Color& color, - float dash_len, float gap_len, float thickness) const; + // optional dispatch — enables when() conditional widgets + template + void dispatch(const std::optional& w) + { + if (w) + dispatch(*w); + } + + void dispatch(const widget::Box& w); + void dispatch(const widget::CorneredBox& w); + void dispatch(const widget::DashedBox& w); + void dispatch(const widget::RightSide& w); + void dispatch(const widget::LeftSide& w); + void dispatch(const widget::TopSide& w); + void dispatch(const widget::BottomSide& w); + void dispatch(const widget::Skeleton& w); + void dispatch(const widget::SnapLine& w); + void draw_outlined_text(const Vector2& position, const Color& color, const std::string_view& text); + void draw_dashed_line(const Vector2& from, const Vector2& to, const Color& color, float dash_len, + float gap_len, float thickness) const; void draw_dashed_fill(const Vector2& origin, const Vector2& step_dir, - const Vector2& perp_dir, float full_len, float filled_len, - const Color& fill_color, const Color& split_color, - float dash_len, float gap_len) const; + const Vector2& perp_dir, float full_len, float filled_len, const Color& fill_color, + const Color& split_color, float dash_len, float gap_len) const; + CanvasBox m_canvas; Vector2 m_text_cursor_right; Vector2 m_text_cursor_top; @@ -131,4 +165,4 @@ namespace omath::hud Vector2 m_text_cursor_left; std::shared_ptr m_renderer; }; -} // namespace omath::hud \ No newline at end of file +} // namespace omath::hud diff --git a/include/omath/hud/entity_overlay_widgets.hpp b/include/omath/hud/entity_overlay_widgets.hpp new file mode 100644 index 00000000..0cf8e47f --- /dev/null +++ b/include/omath/hud/entity_overlay_widgets.hpp @@ -0,0 +1,142 @@ +// +// Created by orange on 15.03.2026. +// +#pragma once +#include "omath/linear_algebra/vector2.hpp" +#include "omath/utility/color.hpp" +#include +#include +#include +#include + +namespace omath::hud::widget +{ + // ── Overloaded helper for std::visit ────────────────────────────────────── + template + struct Overloaded : Ts... + { + using Ts::operator()...; + }; + template + Overloaded(Ts...) -> Overloaded; + + // ── Standalone widgets ──────────────────────────────────────────────────── + struct Box + { + Color color; + Color fill{0.f, 0.f, 0.f, 0.f}; + float thickness = 1.f; + }; + + struct CorneredBox + { + Color color; + Color fill{0.f, 0.f, 0.f, 0.f}; + float corner_ratio = 0.2f; + float thickness = 1.f; + }; + + struct DashedBox + { + Color color; + float dash_len = 8.f; + float gap_len = 5.f; + float thickness = 1.f; + }; + + struct Skeleton + { + Color color; + float thickness = 1.f; + }; + struct SnapLine + { + Vector2 start; + Color color; + float width; + }; + + // ── Side-agnostic widgets (used inside XxxSide containers) ──────────────── + + /// A filled bar. `size` is width for left/right sides, height for top/bottom. + struct Bar + { + Color color; + Color outline; + Color bg; + float size; + float ratio; + float offset = 5.f; + }; + + /// A dashed bar. Same field semantics as Bar plus dash parameters. + struct DashedBar + { + Color color; + Color outline; + Color bg; + float size; + float ratio; + float dash_len; + float gap_len; + float offset = 5.f; + }; + + struct Label + { + Color color; + float offset; + bool outlined; + std::string_view text; + }; + + /// Wraps a Label to request horizontal centering (only applied in TopSide / BottomSide). + template + struct Centered + { + W child; + }; + template + Centered(W) -> Centered; + + // ── Side widget variant ─────────────────────────────────────────────────── + struct None {}; ///< No-op placeholder — used by widget::when for disabled elements. + using SideWidget = std::variant>; + + // ── Side containers ─────────────────────────────────────────────────────── + // Storing std::initializer_list is safe here: the backing array + // is a const SideWidget[] on the caller's stack whose lifetime matches the + // temporary side-container object, which is consumed within the same + // full-expression by EntityOverlay::dispatch. No heap allocation occurs. + + struct RightSide { std::initializer_list children; RightSide(std::initializer_list c) : children(c) {} }; + struct LeftSide { std::initializer_list children; LeftSide(std::initializer_list c) : children(c) {} }; + struct TopSide { std::initializer_list children; TopSide(std::initializer_list c) : children(c) {} }; + struct BottomSide { std::initializer_list children; BottomSide(std::initializer_list c) : children(c) {} }; + +} // namespace omath::hud::widget + +namespace omath::hud::widget +{ + /// Inside XxxSide containers: returns the widget as a SideWidget when condition is true, + /// or None{} otherwise. Preferred over hud::when for types inside the SideWidget variant. + template + requires std::constructible_from + SideWidget when(const bool condition, W widget) + { + if (condition) return SideWidget{std::move(widget)}; + return None{}; + } +} // namespace omath::hud::widget + +namespace omath::hud +{ + /// Top-level: returns an engaged optional when condition is true, std::nullopt otherwise. + /// Designed for use with EntityOverlay::contents() for top-level widget types. + template + std::optional when(const bool condition, W widget) + { + if (condition) return std::move(widget); + return std::nullopt; + } +} // namespace omath::hud diff --git a/source/hud/entity_overlay.cpp b/source/hud/entity_overlay.cpp index 2202dfb0..05f63158 100644 --- a/source/hud/entity_overlay.cpp +++ b/source/hud/entity_overlay.cpp @@ -5,7 +5,7 @@ namespace omath::hud { - void EntityOverlay::add_2d_box(const Color& box_color, const Color& fill_color, const float thickness) const + EntityOverlay& EntityOverlay::add_2d_box(const Color& box_color, const Color& fill_color, const float thickness) { const auto points = m_canvas.as_array(); @@ -13,9 +13,11 @@ namespace omath::hud if (fill_color.value().w > 0.f) m_renderer->add_filled_polyline({points.data(), points.size()}, fill_color); + + return *this; } - void EntityOverlay::add_cornered_2d_box(const Color& box_color, const Color& fill_color, - const float corner_ratio_len, const float thickness) const + EntityOverlay& EntityOverlay::add_cornered_2d_box(const Color& box_color, const Color& fill_color, + const float corner_ratio_len, const float thickness) { const auto corner_line_length = std::abs((m_canvas.top_left_corner - m_canvas.top_right_corner).x * corner_ratio_len); @@ -50,9 +52,11 @@ namespace omath::hud m_renderer->add_line(m_canvas.bottom_right_corner, m_canvas.bottom_right_corner - Vector2{corner_line_length, 0.f}, box_color, thickness); + + return *this; } - void EntityOverlay::add_right_bar(const Color& color, const Color& outline_color, const Color& bg_color, - const float width, float ratio, const float offset) + EntityOverlay& EntityOverlay::add_right_bar(const Color& color, const Color& outline_color, const Color& bg_color, + const float width, float ratio, const float offset) { ratio = std::clamp(ratio, 0.f, 1.f); const auto max_bar_height = std::abs(m_canvas.top_right_corner.y - m_canvas.bottom_right_corner.y); @@ -65,9 +69,11 @@ namespace omath::hud bar_start + Vector2(width, -max_bar_height), outline_color); m_text_cursor_right.x += offset + width; + + return *this; } - void EntityOverlay::add_left_bar(const Color& color, const Color& outline_color, const Color& bg_color, - const float width, float ratio, const float offset) + EntityOverlay& EntityOverlay::add_left_bar(const Color& color, const Color& outline_color, const Color& bg_color, + const float width, float ratio, const float offset) { ratio = std::clamp(ratio, 0.f, 1.f); const auto max_bar_height = std::abs(m_canvas.top_left_corner.y - m_canvas.bottom_right_corner.y); @@ -80,9 +86,11 @@ namespace omath::hud bar_start + Vector2(width, -max_bar_height), outline_color); m_text_cursor_left.x -= offset + width; + + return *this; } - void EntityOverlay::add_right_label(const Color& color, const float offset, const bool outlined, - const std::string_view& text) + EntityOverlay& EntityOverlay::add_right_label(const Color& color, const float offset, const bool outlined, + const std::string_view& text) { if (outlined) draw_outlined_text(m_text_cursor_right + Vector2{offset, 0.f}, color, text); @@ -90,9 +98,11 @@ namespace omath::hud m_renderer->add_text(m_text_cursor_right + Vector2{offset, 0.f}, color, text.data()); m_text_cursor_right.y += m_renderer->calc_text_size(text.data()).y; + + return *this; } - void EntityOverlay::add_top_label(const Color& color, const float offset, const bool outlined, - const std::string_view text) + EntityOverlay& EntityOverlay::add_top_label(const Color& color, const float offset, const bool outlined, + const std::string_view text) { m_text_cursor_top.y -= m_renderer->calc_text_size(text.data()).y; @@ -100,9 +110,11 @@ namespace omath::hud draw_outlined_text(m_text_cursor_top + Vector2{0.f, -offset}, color, text); else m_renderer->add_text(m_text_cursor_top + Vector2{0.f, -offset}, color, text.data()); + + return *this; } - void EntityOverlay::add_top_bar(const Color& color, const Color& outline_color, const Color& bg_color, - const float height, float ratio, const float offset) + EntityOverlay& EntityOverlay::add_top_bar(const Color& color, const Color& outline_color, const Color& bg_color, + const float height, float ratio, const float offset) { ratio = std::clamp(ratio, 0.f, 1.f); const auto max_bar_width = std::abs(m_canvas.top_left_corner.x - m_canvas.bottom_right_corner.x); @@ -114,36 +126,38 @@ namespace omath::hud m_renderer->add_rectangle(bar_start, bar_start + Vector2(max_bar_width, -height), outline_color); m_text_cursor_top.y -= offset + height; + + return *this; } - void EntityOverlay::add_snap_line(const Vector2& start_pos, const Color& color, const float width) + EntityOverlay& EntityOverlay::add_snap_line(const Vector2& start_pos, const Color& color, const float width) { const Vector2 line_end = m_canvas.bottom_left_corner + Vector2{m_canvas.bottom_right_corner.x - m_canvas.bottom_left_corner.x, 0.f} / 2; m_renderer->add_line(start_pos, line_end, color, width); + + return *this; } void EntityOverlay::draw_dashed_fill(const Vector2& origin, const Vector2& step_dir, - const Vector2& perp_dir, const float full_len, - const float filled_len, const Color& fill_color, - const Color& split_color, const float dash_len, + const Vector2& perp_dir, const float full_len, const float filled_len, + const Color& fill_color, const Color& split_color, const float dash_len, const float gap_len) const { if (full_len <= 0.f) return; - const float step = dash_len + gap_len; - const float n = std::floor((full_len + gap_len) / step); + const float step = dash_len + gap_len; + const float n = std::floor((full_len + gap_len) / step); if (n < 1.f) return; - const float used = n * dash_len + (n - 1.f) * gap_len; + const float used = n * dash_len + (n - 1.f) * gap_len; const float offset = (full_len - used) / 2.f; const auto fill_rect = [&](const Vector2& a, const Vector2& b, const Color& c) { - m_renderer->add_filled_rectangle( - {std::min(a.x, b.x), std::min(a.y, b.y)}, - {std::max(a.x, b.x), std::max(a.y, b.y)}, c); + m_renderer->add_filled_rectangle({std::min(a.x, b.x), std::min(a.y, b.y)}, + {std::max(a.x, b.x), std::max(a.y, b.y)}, c); }; // Draw split lines (gaps) across the full bar first @@ -154,9 +168,9 @@ namespace omath::hud for (float i = 0.f; i < n; ++i) { const float dash_start = offset + i * step; - const float dash_end = dash_start + dash_len; - const float gap_start = dash_end; - const float gap_end = dash_start + step; + const float dash_end = dash_start + dash_len; + const float gap_start = dash_end; + const float gap_end = dash_start + step; // Fill dash only up to filled_len if (dash_start < filled_len) @@ -181,106 +195,115 @@ namespace omath::hud fill_rect(origin + step_dir * trail_start, origin + step_dir * full_len + perp_dir, split_color); } - void EntityOverlay::add_right_dashed_bar(const Color& color, const Color& outline_color, const Color& bg_color, - const float width, float ratio, const float dash_len, - const float gap_len, const float offset) + EntityOverlay& EntityOverlay::add_right_dashed_bar(const Color& color, const Color& outline_color, + const Color& bg_color, const float width, float ratio, + const float dash_len, const float gap_len, const float offset) { ratio = std::clamp(ratio, 0.f, 1.f); - const float height = std::abs(m_canvas.top_right_corner.y - m_canvas.bottom_right_corner.y); - const auto bar_start = Vector2{m_text_cursor_right.x + offset, m_canvas.bottom_right_corner.y}; + const float height = std::abs(m_canvas.top_right_corner.y - m_canvas.bottom_right_corner.y); + const auto bar_start = Vector2{m_text_cursor_right.x + offset, m_canvas.bottom_right_corner.y}; m_renderer->add_filled_rectangle(bar_start, bar_start + Vector2{width, -height}, bg_color); - draw_dashed_fill(bar_start, {0.f, -1.f}, {width, 0.f}, height, height * ratio, - color, outline_color, dash_len, gap_len); - m_renderer->add_rectangle(bar_start - Vector2{1.f, 0.f}, - bar_start + Vector2{width, -height}, outline_color); + draw_dashed_fill(bar_start, {0.f, -1.f}, {width, 0.f}, height, height * ratio, color, outline_color, dash_len, + gap_len); + m_renderer->add_rectangle(bar_start - Vector2{1.f, 0.f}, bar_start + Vector2{width, -height}, + outline_color); m_text_cursor_right.x += offset + width; + + return *this; } - void EntityOverlay::add_left_dashed_bar(const Color& color, const Color& outline_color, const Color& bg_color, - const float width, float ratio, const float dash_len, - const float gap_len, const float offset) + EntityOverlay& EntityOverlay::add_left_dashed_bar(const Color& color, const Color& outline_color, + const Color& bg_color, const float width, float ratio, + const float dash_len, const float gap_len, const float offset) { ratio = std::clamp(ratio, 0.f, 1.f); - const float height = std::abs(m_canvas.top_left_corner.y - m_canvas.bottom_left_corner.y); - const auto bar_start = Vector2{m_text_cursor_left.x - (offset + width), m_canvas.bottom_left_corner.y}; + const float height = std::abs(m_canvas.top_left_corner.y - m_canvas.bottom_left_corner.y); + const auto bar_start = Vector2{m_text_cursor_left.x - (offset + width), m_canvas.bottom_left_corner.y}; m_renderer->add_filled_rectangle(bar_start, bar_start + Vector2{width, -height}, bg_color); - draw_dashed_fill(bar_start, {0.f, -1.f}, {width, 0.f}, height, height * ratio, - color, outline_color, dash_len, gap_len); - m_renderer->add_rectangle(bar_start - Vector2{1.f, 0.f}, - bar_start + Vector2{width, -height}, outline_color); + draw_dashed_fill(bar_start, {0.f, -1.f}, {width, 0.f}, height, height * ratio, color, outline_color, dash_len, + gap_len); + m_renderer->add_rectangle(bar_start - Vector2{1.f, 0.f}, bar_start + Vector2{width, -height}, + outline_color); m_text_cursor_left.x -= offset + width; + + return *this; } - void EntityOverlay::add_top_dashed_bar(const Color& color, const Color& outline_color, const Color& bg_color, - const float height, float ratio, const float dash_len, - const float gap_len, const float offset) + EntityOverlay& EntityOverlay::add_top_dashed_bar(const Color& color, const Color& outline_color, + const Color& bg_color, const float height, float ratio, + const float dash_len, const float gap_len, const float offset) { ratio = std::clamp(ratio, 0.f, 1.f); - const float bar_w = std::abs(m_canvas.top_left_corner.x - m_canvas.top_right_corner.x); - const auto bar_start = Vector2{m_canvas.top_left_corner.x, m_text_cursor_top.y - offset}; + const float bar_w = std::abs(m_canvas.top_left_corner.x - m_canvas.top_right_corner.x); + const auto bar_start = Vector2{m_canvas.top_left_corner.x, m_text_cursor_top.y - offset}; m_renderer->add_filled_rectangle(bar_start, bar_start + Vector2{bar_w, -height}, bg_color); - draw_dashed_fill(bar_start, {1.f, 0.f}, {0.f, -height}, bar_w, bar_w * ratio, - color, outline_color, dash_len, gap_len); + draw_dashed_fill(bar_start, {1.f, 0.f}, {0.f, -height}, bar_w, bar_w * ratio, color, outline_color, dash_len, + gap_len); m_renderer->add_rectangle(bar_start, bar_start + Vector2{bar_w, -height}, outline_color); m_text_cursor_top.y -= offset + height; + + return *this; } - void EntityOverlay::add_bottom_dashed_bar(const Color& color, const Color& outline_color, const Color& bg_color, - const float height, float ratio, const float dash_len, - const float gap_len, const float offset) + EntityOverlay& EntityOverlay::add_bottom_dashed_bar(const Color& color, const Color& outline_color, + const Color& bg_color, const float height, float ratio, + const float dash_len, const float gap_len, const float offset) { ratio = std::clamp(ratio, 0.f, 1.f); - const float bar_w = std::abs(m_canvas.bottom_left_corner.x - m_canvas.bottom_right_corner.x); - const auto bar_start = Vector2{m_canvas.bottom_left_corner.x, m_text_cursor_bottom.y + offset}; + const float bar_w = std::abs(m_canvas.bottom_left_corner.x - m_canvas.bottom_right_corner.x); + const auto bar_start = Vector2{m_canvas.bottom_left_corner.x, m_text_cursor_bottom.y + offset}; m_renderer->add_filled_rectangle(bar_start, bar_start + Vector2{bar_w, height}, bg_color); - draw_dashed_fill(bar_start, {1.f, 0.f}, {0.f, height}, bar_w, bar_w * ratio, - color, outline_color, dash_len, gap_len); + draw_dashed_fill(bar_start, {1.f, 0.f}, {0.f, height}, bar_w, bar_w * ratio, color, outline_color, dash_len, + gap_len); m_renderer->add_rectangle(bar_start, bar_start + Vector2{bar_w, height}, outline_color); m_text_cursor_bottom.y += offset + height; + + return *this; } - void EntityOverlay::add_skeleton(const Color& color, const float thickness) const + EntityOverlay& EntityOverlay::add_skeleton(const Color& color, const float thickness) { // Maps normalized (rx in [0,1], ry in [0,1]) to canvas screen position const auto joint = [&](const float rx, const float ry) -> Vector2 { - const auto top = m_canvas.top_left_corner - + (m_canvas.top_right_corner - m_canvas.top_left_corner) * rx; - const auto bot = m_canvas.bottom_left_corner - + (m_canvas.bottom_right_corner - m_canvas.bottom_left_corner) * rx; + const auto top = m_canvas.top_left_corner + (m_canvas.top_right_corner - m_canvas.top_left_corner) * rx; + const auto bot = + m_canvas.bottom_left_corner + (m_canvas.bottom_right_corner - m_canvas.bottom_left_corner) * rx; return top + (bot - top) * ry; }; - using B = std::pair, std::pair>; + using B = std::pair, std::pair>; static constexpr std::array k_bones{{ - // Spine - {{0.50f, 0.13f}, {0.50f, 0.22f}}, // head → neck - {{0.50f, 0.22f}, {0.50f, 0.38f}}, // neck → chest - {{0.50f, 0.38f}, {0.50f, 0.55f}}, // chest → pelvis - // Left arm - {{0.50f, 0.22f}, {0.25f, 0.25f}}, // neck → L shoulder - {{0.25f, 0.25f}, {0.13f, 0.42f}}, // L shoulder → L elbow - {{0.13f, 0.42f}, {0.08f, 0.56f}}, // L elbow → L hand - // Right arm - {{0.50f, 0.22f}, {0.75f, 0.25f}}, // neck → R shoulder - {{0.75f, 0.25f}, {0.87f, 0.42f}}, // R shoulder → R elbow - {{0.87f, 0.42f}, {0.92f, 0.56f}}, // R elbow → R hand - // Left leg - {{0.50f, 0.55f}, {0.36f, 0.58f}}, // pelvis → L hip - {{0.36f, 0.58f}, {0.32f, 0.77f}}, // L hip → L knee - {{0.32f, 0.77f}, {0.27f, 0.97f}}, // L knee → L foot - // Right leg - {{0.50f, 0.55f}, {0.64f, 0.58f}}, // pelvis → R hip - {{0.64f, 0.58f}, {0.68f, 0.77f}}, // R hip → R knee - {{0.68f, 0.77f}, {0.73f, 0.97f}}, // R knee → R foot + // Spine + {{0.50f, 0.13f}, {0.50f, 0.22f}}, // head → neck + {{0.50f, 0.22f}, {0.50f, 0.38f}}, // neck → chest + {{0.50f, 0.38f}, {0.50f, 0.55f}}, // chest → pelvis + // Left arm + {{0.50f, 0.22f}, {0.25f, 0.25f}}, // neck → L shoulder + {{0.25f, 0.25f}, {0.13f, 0.42f}}, // L shoulder → L elbow + {{0.13f, 0.42f}, {0.08f, 0.56f}}, // L elbow → L hand + // Right arm + {{0.50f, 0.22f}, {0.75f, 0.25f}}, // neck → R shoulder + {{0.75f, 0.25f}, {0.87f, 0.42f}}, // R shoulder → R elbow + {{0.87f, 0.42f}, {0.92f, 0.56f}}, // R elbow → R hand + // Left leg + {{0.50f, 0.55f}, {0.36f, 0.58f}}, // pelvis → L hip + {{0.36f, 0.58f}, {0.32f, 0.77f}}, // L hip → L knee + {{0.32f, 0.77f}, {0.27f, 0.97f}}, // L knee → L foot + // Right leg + {{0.50f, 0.55f}, {0.64f, 0.58f}}, // pelvis → R hip + {{0.64f, 0.58f}, {0.68f, 0.77f}}, // R hip → R knee + {{0.68f, 0.77f}, {0.73f, 0.97f}}, // R knee → R foot }}; for (const auto& [a, b] : k_bones) m_renderer->add_line(joint(a.first, a.second), joint(b.first, b.second), color, thickness); + + return *this; } void EntityOverlay::draw_dashed_line(const Vector2& from, const Vector2& to, const Color& color, @@ -290,31 +313,30 @@ namespace omath::hud if (total <= 0.f) return; - const auto dir = (to - from).normalized(); + const auto dir = (to - from).normalized(); const float step = dash_len + gap_len; - const float n_dashes = std::floor((total + gap_len) / step); + const float n_dashes = std::floor((total + gap_len) / step); if (n_dashes < 1.f) return; - const float used = n_dashes * dash_len + (n_dashes - 1.f) * gap_len; + const float used = n_dashes * dash_len + (n_dashes - 1.f) * gap_len; const float offset = (total - used) / 2.f; for (float i = 0.f; i < n_dashes; ++i) { - const float pos = offset + i * step; + const float pos = offset + i * step; const auto dash_start = from + dir * pos; - const auto dash_end = from + dir * std::min(pos + dash_len, total); + const auto dash_end = from + dir * std::min(pos + dash_len, total); m_renderer->add_line(dash_start, dash_end, color, thickness); } } - void EntityOverlay::add_dashed_box(const Color& color, const float dash_len, const float gap_len, - const float thickness) const + EntityOverlay& EntityOverlay::add_dashed_box(const Color& color, const float dash_len, const float gap_len, + const float thickness) { - const float min_edge = std::min( - (m_canvas.top_right_corner - m_canvas.top_left_corner).length(), - (m_canvas.bottom_right_corner - m_canvas.top_right_corner).length()); + const float min_edge = std::min((m_canvas.top_right_corner - m_canvas.top_left_corner).length(), + (m_canvas.bottom_right_corner - m_canvas.top_right_corner).length()); const float corner_len = std::min(dash_len, min_edge / 2.f); const auto draw_edge = [&](const Vector2& from, const Vector2& to) @@ -326,10 +348,12 @@ namespace omath::hud m_renderer->add_line(to - dir * corner_len, to, color, thickness); }; - draw_edge(m_canvas.top_left_corner, m_canvas.top_right_corner); - draw_edge(m_canvas.top_right_corner, m_canvas.bottom_right_corner); - draw_edge(m_canvas.bottom_right_corner,m_canvas.bottom_left_corner); + draw_edge(m_canvas.top_left_corner, m_canvas.top_right_corner); + draw_edge(m_canvas.top_right_corner, m_canvas.bottom_right_corner); + draw_edge(m_canvas.bottom_right_corner, m_canvas.bottom_left_corner); draw_edge(m_canvas.bottom_left_corner, m_canvas.top_left_corner); + + return *this; } void EntityOverlay::draw_outlined_text(const Vector2& position, const Color& color, @@ -343,8 +367,8 @@ namespace omath::hud m_renderer->add_text(position + outline_offset, Color{0.f, 0.f, 0.f, 1.f}, text.data()); m_renderer->add_text(position, color, text.data()); } - void EntityOverlay::add_bottom_bar(const Color& color, const Color& outline_color, const Color& bg_color, - const float height, float ratio, const float offset) + EntityOverlay& EntityOverlay::add_bottom_bar(const Color& color, const Color& outline_color, const Color& bg_color, + const float height, float ratio, const float offset) { ratio = std::clamp(ratio, 0.f, 1.f); const auto max_bar_width = std::abs(m_canvas.bottom_right_corner.x - m_canvas.bottom_left_corner.x); @@ -355,10 +379,12 @@ namespace omath::hud m_renderer->add_rectangle(bar_start, bar_start + Vector2(max_bar_width, height), outline_color); m_text_cursor_bottom.y += offset + height; + + return *this; } - void EntityOverlay::add_bottom_label(const Color& color, const float offset, const bool outlined, - const std::string_view text) + EntityOverlay& EntityOverlay::add_bottom_label(const Color& color, const float offset, const bool outlined, + const std::string_view text) { const auto text_size = m_renderer->calc_text_size(text); @@ -368,10 +394,12 @@ namespace omath::hud m_renderer->add_text(m_text_cursor_bottom + Vector2{0.f, offset}, color, text); m_text_cursor_bottom.y += text_size.y; + + return *this; } - void EntityOverlay::add_left_label(const Color& color, const float offset, const bool outlined, - const std::string_view& text) + EntityOverlay& EntityOverlay::add_left_label(const Color& color, const float offset, const bool outlined, + const std::string_view& text) { const auto text_size = m_renderer->calc_text_size(text); const auto pos = m_text_cursor_left + Vector2{-(offset + text_size.x), 0.f}; @@ -382,10 +410,12 @@ namespace omath::hud m_renderer->add_text(pos, color, text); m_text_cursor_left.y += text_size.y; + + return *this; } - void EntityOverlay::add_centered_bottom_label(const Color& color, const float offset, const bool outlined, - const std::string_view& text) + EntityOverlay& EntityOverlay::add_centered_bottom_label(const Color& color, const float offset, const bool outlined, + const std::string_view& text) { const auto text_size = m_renderer->calc_text_size(text); const auto box_center_x = @@ -398,10 +428,12 @@ namespace omath::hud m_renderer->add_text(pos, color, text); m_text_cursor_bottom.y += text_size.y; + + return *this; } - void EntityOverlay::add_centered_top_label(const Color& color, const float offset, const bool outlined, - const std::string_view& text) + EntityOverlay& EntityOverlay::add_centered_top_label(const Color& color, const float offset, const bool outlined, + const std::string_view& text) { const auto text_size = m_renderer->calc_text_size(text); const auto box_center_x = @@ -414,6 +446,8 @@ namespace omath::hud draw_outlined_text(pos, color, text); else m_renderer->add_text(pos, color, text); + + return *this; } EntityOverlay::EntityOverlay(const Vector2& top, const Vector2& bottom, @@ -423,4 +457,85 @@ namespace omath::hud m_text_cursor_left(m_canvas.top_left_corner), m_renderer(renderer) { } + // ── widget dispatch ─────────────────────────────────────────────────────── + void EntityOverlay::dispatch(const widget::Box& w) + { add_2d_box(w.color, w.fill, w.thickness); } + + void EntityOverlay::dispatch(const widget::CorneredBox& w) + { add_cornered_2d_box(w.color, w.fill, w.corner_ratio, w.thickness); } + + void EntityOverlay::dispatch(const widget::DashedBox& w) + { add_dashed_box(w.color, w.dash_len, w.gap_len, w.thickness); } + + void EntityOverlay::dispatch(const widget::Skeleton& w) + { add_skeleton(w.color, w.thickness); } + + void EntityOverlay::dispatch(const widget::SnapLine& w) + { add_snap_line(w.start, w.color, w.width); } + + // ── Side container dispatch ─────────────────────────────────────────────── + void EntityOverlay::dispatch(const widget::RightSide& s) + { + for (const auto& child : s.children) + std::visit(widget::Overloaded{ + [](const widget::None&) {}, + [this](const widget::Bar& w) + { add_right_bar(w.color, w.outline, w.bg, w.size, w.ratio, w.offset); }, + [this](const widget::DashedBar& w) + { add_right_dashed_bar(w.color, w.outline, w.bg, w.size, w.ratio, w.dash_len, w.gap_len, w.offset); }, + [this](const widget::Label& w) + { add_right_label(w.color, w.offset, w.outlined, w.text); }, + [this](const widget::Centered& w) + { add_right_label(w.child.color, w.child.offset, w.child.outlined, w.child.text); }, + }, child); + } + + void EntityOverlay::dispatch(const widget::LeftSide& s) + { + for (const auto& child : s.children) + std::visit(widget::Overloaded{ + [](const widget::None&) {}, + [this](const widget::Bar& w) + { add_left_bar(w.color, w.outline, w.bg, w.size, w.ratio, w.offset); }, + [this](const widget::DashedBar& w) + { add_left_dashed_bar(w.color, w.outline, w.bg, w.size, w.ratio, w.dash_len, w.gap_len, w.offset); }, + [this](const widget::Label& w) + { add_left_label(w.color, w.offset, w.outlined, w.text); }, + [this](const widget::Centered& w) + { add_left_label(w.child.color, w.child.offset, w.child.outlined, w.child.text); }, + }, child); + } + + void EntityOverlay::dispatch(const widget::TopSide& s) + { + for (const auto& child : s.children) + std::visit(widget::Overloaded{ + [](const widget::None&) {}, + [this](const widget::Bar& w) + { add_top_bar(w.color, w.outline, w.bg, w.size, w.ratio, w.offset); }, + [this](const widget::DashedBar& w) + { add_top_dashed_bar(w.color, w.outline, w.bg, w.size, w.ratio, w.dash_len, w.gap_len, w.offset); }, + [this](const widget::Label& w) + { add_top_label(w.color, w.offset, w.outlined, w.text); }, + [this](const widget::Centered& w) + { add_centered_top_label(w.child.color, w.child.offset, w.child.outlined, w.child.text); }, + }, child); + } + + void EntityOverlay::dispatch(const widget::BottomSide& s) + { + for (const auto& child : s.children) + std::visit(widget::Overloaded{ + [](const widget::None&) {}, + [this](const widget::Bar& w) + { add_bottom_bar(w.color, w.outline, w.bg, w.size, w.ratio, w.offset); }, + [this](const widget::DashedBar& w) + { add_bottom_dashed_bar(w.color, w.outline, w.bg, w.size, w.ratio, w.dash_len, w.gap_len, w.offset); }, + [this](const widget::Label& w) + { add_bottom_label(w.color, w.offset, w.outlined, w.text); }, + [this](const widget::Centered& w) + { add_centered_bottom_label(w.child.color, w.child.offset, w.child.outlined, w.child.text); }, + }, child); + } + } // namespace omath::hud \ No newline at end of file