Merge pull request #168 from orange-cpp/feature/hud_declarative

Feature/hud declarative
This commit is contained in:
2026-03-15 20:02:32 +03:00
committed by GitHub
5 changed files with 584 additions and 299 deletions

View File

@@ -75,8 +75,10 @@ namespace imgui_desktop::gui
if (ImGui::CollapsingHeader("Box", ImGuiTreeNodeFlags_DefaultOpen)) if (ImGui::CollapsingHeader("Box", ImGuiTreeNodeFlags_DefaultOpen))
{ {
ImGui::Checkbox("Box", &m_show_box); ImGui::SameLine(); ImGui::Checkbox("Box##chk", &m_show_box);
ImGui::Checkbox("Cornered", &m_show_cornered_box); ImGui::SameLine(); ImGui::SameLine();
ImGui::Checkbox("Cornered", &m_show_cornered_box);
ImGui::SameLine();
ImGui::Checkbox("Dashed", &m_show_dashed_box); ImGui::Checkbox("Dashed", &m_show_dashed_box);
ImGui::ColorEdit4("Color##box", reinterpret_cast<float*>(&m_box_color), ImGuiColorEditFlags_NoInputs); ImGui::ColorEdit4("Color##box", reinterpret_cast<float*>(&m_box_color), ImGuiColorEditFlags_NoInputs);
ImGui::ColorEdit4("Fill##box", reinterpret_cast<float*>(&m_box_fill), ImGuiColorEditFlags_NoInputs); ImGui::ColorEdit4("Fill##box", reinterpret_cast<float*>(&m_box_fill), ImGuiColorEditFlags_NoInputs);
@@ -93,17 +95,22 @@ namespace imgui_desktop::gui
{ {
ImGui::ColorEdit4("Color##bar", reinterpret_cast<float*>(&m_bar_color), ImGuiColorEditFlags_NoInputs); ImGui::ColorEdit4("Color##bar", reinterpret_cast<float*>(&m_bar_color), ImGuiColorEditFlags_NoInputs);
ImGui::ColorEdit4("BG##bar", reinterpret_cast<float*>(&m_bar_bg_color), ImGuiColorEditFlags_NoInputs); ImGui::ColorEdit4("BG##bar", reinterpret_cast<float*>(&m_bar_bg_color), ImGuiColorEditFlags_NoInputs);
ImGui::ColorEdit4("Outline##bar", reinterpret_cast<float*>(&m_bar_outline_color), ImGuiColorEditFlags_NoInputs); ImGui::ColorEdit4("Outline##bar", reinterpret_cast<float*>(&m_bar_outline_color),
ImGuiColorEditFlags_NoInputs);
ImGui::SliderFloat("Width##bar", &m_bar_width, 1.f, 20.f); ImGui::SliderFloat("Width##bar", &m_bar_width, 1.f, 20.f);
ImGui::SliderFloat("Value##bar", &m_bar_value, 0.f, 1.f); ImGui::SliderFloat("Value##bar", &m_bar_value, 0.f, 1.f);
ImGui::SliderFloat("Offset##bar", &m_bar_offset, 1.f, 20.f); ImGui::SliderFloat("Offset##bar", &m_bar_offset, 1.f, 20.f);
ImGui::Checkbox("Right##bar", &m_show_right_bar); ImGui::SameLine(); ImGui::Checkbox("Right##bar", &m_show_right_bar);
ImGui::SameLine();
ImGui::Checkbox("Left##bar", &m_show_left_bar); ImGui::Checkbox("Left##bar", &m_show_left_bar);
ImGui::Checkbox("Top##bar", &m_show_top_bar); ImGui::SameLine(); ImGui::Checkbox("Top##bar", &m_show_top_bar);
ImGui::SameLine();
ImGui::Checkbox("Bottom##bar", &m_show_bottom_bar); ImGui::Checkbox("Bottom##bar", &m_show_bottom_bar);
ImGui::Checkbox("Right dashed##bar", &m_show_right_dashed_bar); ImGui::SameLine(); ImGui::Checkbox("Right dashed##bar", &m_show_right_dashed_bar);
ImGui::SameLine();
ImGui::Checkbox("Left dashed##bar", &m_show_left_dashed_bar); ImGui::Checkbox("Left dashed##bar", &m_show_left_dashed_bar);
ImGui::Checkbox("Top dashed##bar", &m_show_top_dashed_bar); ImGui::SameLine(); ImGui::Checkbox("Top dashed##bar", &m_show_top_dashed_bar);
ImGui::SameLine();
ImGui::Checkbox("Bot dashed##bar", &m_show_bottom_dashed_bar); 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 len##bar", &m_bar_dash_len, 2.f, 20.f);
ImGui::SliderFloat("Dash gap##bar", &m_bar_dash_gap, 1.f, 15.f); ImGui::SliderFloat("Dash gap##bar", &m_bar_dash_gap, 1.f, 15.f);
@@ -113,11 +120,14 @@ namespace imgui_desktop::gui
{ {
ImGui::Checkbox("Outlined", &m_outlined); ImGui::Checkbox("Outlined", &m_outlined);
ImGui::SliderFloat("Offset##lbl", &m_label_offset, 0.f, 15.f); ImGui::SliderFloat("Offset##lbl", &m_label_offset, 0.f, 15.f);
ImGui::Checkbox("Right##lbl", &m_show_right_labels); ImGui::SameLine(); ImGui::Checkbox("Right##lbl", &m_show_right_labels);
ImGui::SameLine();
ImGui::Checkbox("Left##lbl", &m_show_left_labels); ImGui::Checkbox("Left##lbl", &m_show_left_labels);
ImGui::Checkbox("Top##lbl", &m_show_top_labels); ImGui::SameLine(); ImGui::Checkbox("Top##lbl", &m_show_top_labels);
ImGui::SameLine();
ImGui::Checkbox("Bottom##lbl", &m_show_bottom_labels); ImGui::Checkbox("Bottom##lbl", &m_show_bottom_labels);
ImGui::Checkbox("Ctr top##lbl", &m_show_centered_top); ImGui::SameLine(); ImGui::Checkbox("Ctr top##lbl", &m_show_centered_top);
ImGui::SameLine();
ImGui::Checkbox("Ctr bot##lbl", &m_show_centered_bottom); ImGui::Checkbox("Ctr bot##lbl", &m_show_centered_bottom);
} }
@@ -141,77 +151,64 @@ namespace imgui_desktop::gui
void MainWindow::draw_overlay() void MainWindow::draw_overlay()
{ {
using namespace omath::hud::widget;
using omath::hud::when;
const auto* vp = ImGui::GetMainViewport(); const auto* vp = ImGui::GetMainViewport();
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 ent( omath::hud::EntityOverlay({m_entity_x, m_entity_top_y}, {m_entity_x, m_entity_bottom_y},
{m_entity_x, m_entity_top_y}, {m_entity_x, m_entity_bottom_y}, std::make_shared<omath::hud::ImguiHudRenderer>())
std::make_shared<omath::hud::ImguiHudRenderer>()); .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}),
draw_boxes(ent); RightSide
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) when(m_show_right_bar, bar),
ent.add_2d_box(m_box_color, m_box_fill, m_box_thickness); when(m_show_right_dashed_bar, dbar),
if (m_show_cornered_box) when(m_show_right_labels,
ent.add_cornered_2d_box(omath::Color::from_rgba(255, 0, 255, 255), m_box_fill, m_corner_ratio, m_box_thickness); Label{{0.f, 1.f, 0.f, 1.f}, m_label_offset, m_outlined, "Health: 100/100"}),
if (m_show_dashed_box) when(m_show_right_labels,
ent.add_dashed_box(m_dash_color, m_dash_len, m_dash_gap, m_dash_thickness); 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*"}),
void MainWindow::draw_bars(omath::hud::EntityOverlay& ent) const },
LeftSide
{ {
if (m_show_right_bar) when(m_show_left_bar, 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); when(m_show_left_dashed_bar, dbar),
if (m_show_left_bar) when(m_show_left_labels, Label{omath::Color::from_rgba(255, 128, 0, 255),
ent.add_left_bar(m_bar_color, m_bar_outline_color, m_bar_bg_color, m_bar_width, m_bar_value, m_bar_offset); m_label_offset, m_outlined, "Armor: 75"}),
if (m_show_top_bar) when(m_show_left_labels, Label{omath::Color::from_rgba(0, 200, 255, 255),
ent.add_top_bar(m_bar_color, m_bar_outline_color, m_bar_bg_color, m_bar_width, m_bar_value, m_bar_offset); m_label_offset, m_outlined, "Level: 42"}),
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); TopSide
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) 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
{ {
ent.add_right_label({0.f, 1.f, 0.f, 1.f}, m_label_offset, m_outlined, "Health: {}/100", 100); when(m_show_bottom_bar, bar),
ent.add_right_label({1.f, 0.f, 0.f, 1.f}, m_label_offset, m_outlined, "Shield: {}/125", 125); when(m_show_bottom_dashed_bar, dbar),
ent.add_right_label({1.f, 0.f, 1.f, 1.f}, m_label_offset, m_outlined, "*LOCKED*"); when(m_show_centered_bottom, Centered{Label{omath::Color::from_rgba(255, 255, 255, 255),
} m_label_offset, m_outlined, "PlayerName"}}),
if (m_show_left_labels) when(m_show_bottom_labels, Label{omath::Color::from_rgba(200, 200, 0, 255),
{ m_label_offset, m_outlined, "42m"}),
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"); 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}));
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");
} }
void MainWindow::present() void MainWindow::present()

View File

@@ -19,9 +19,6 @@ namespace imgui_desktop::gui
private: private:
void draw_controls(); void draw_controls();
void draw_overlay(); 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(); void present();
GLFWwindow* m_window = nullptr; GLFWwindow* m_window = nullptr;

View File

@@ -3,11 +3,13 @@
// //
#pragma once #pragma once
#include "canvas_box.hpp" #include "canvas_box.hpp"
#include "entity_overlay_widgets.hpp"
#include "hud_renderer_interface.hpp" #include "hud_renderer_interface.hpp"
#include "omath/linear_algebra/vector2.hpp" #include "omath/linear_algebra/vector2.hpp"
#include "omath/utility/color.hpp" #include "omath/utility/color.hpp"
#include <memory> #include <memory>
#include <string_view> #include <string_view>
namespace omath::hud namespace omath::hud
{ {
class EntityOverlay final class EntityOverlay final
@@ -16,114 +18,146 @@ namespace omath::hud
EntityOverlay(const Vector2<float>& top, const Vector2<float>& bottom, EntityOverlay(const Vector2<float>& top, const Vector2<float>& bottom,
const std::shared_ptr<HudRendererInterface>& renderer); const std::shared_ptr<HudRendererInterface>& renderer);
void add_2d_box(const Color& box_color, const Color& fill_color = Color{0.f, 0.f, 0.f, 0.f}, // ── Boxes ────────────────────────────────────────────────────────
float thickness = 1.f) const; 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_cornered_2d_box(const Color& box_color, const Color& fill_color = Color{0.f, 0.f, 0.f, 0.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) const; float corner_ratio_len = 0.2f, float thickness = 1.f);
void add_right_bar(const Color& color, const Color& outline_color, const Color& bg_color, float width, EntityOverlay& add_dashed_box(const Color& color, float dash_len = 8.f, float gap_len = 5.f,
float thickness = 1.f);
// ── Bars ─────────────────────────────────────────────────────────
EntityOverlay& add_right_bar(const Color& color, const Color& outline_color, const Color& bg_color, float width,
float ratio, float offset = 5.f); float ratio, float offset = 5.f);
void add_left_bar(const Color& color, const Color& outline_color, const Color& bg_color, float width, EntityOverlay& add_left_bar(const Color& color, const Color& outline_color, const Color& bg_color, float width,
float ratio, float offset = 5.f); float ratio, float offset = 5.f);
template<typename... Args> EntityOverlay& add_top_bar(const Color& color, const Color& outline_color, const Color& bg_color, float height,
void add_right_label(const Color& color, const float offset, const bool outlined, float ratio, float offset = 5.f);
std::format_string<Args...> fmt, Args&&... args)
{
const std::string label = std::vformat(fmt.get(), std::make_format_args(args...));
add_right_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_right_label(const Color& color, float offset, bool outlined, const 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);
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);
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);
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);
// ── Labels ───────────────────────────────────────────────────────
EntityOverlay& add_right_label(const Color& color, float offset, bool outlined, const std::string_view& text);
EntityOverlay& add_left_label(const Color& color, float offset, bool outlined, const std::string_view& text);
EntityOverlay& add_top_label(const Color& color, float offset, bool outlined, std::string_view text);
EntityOverlay& add_bottom_label(const Color& color, float offset, bool outlined, std::string_view text);
EntityOverlay& add_centered_top_label(const Color& color, float offset, bool outlined,
const std::string_view& text);
EntityOverlay& add_centered_bottom_label(const Color& color, float offset, bool outlined,
const std::string_view& text);
template<typename... Args> template<typename... Args>
void add_top_label(const Color& color, const float offset, const bool outlined, std::format_string<Args...> fmt, EntityOverlay& add_right_label(const Color& color, float offset, bool outlined, std::format_string<Args...> fmt,
Args&&... args) Args&&... args)
{ {
const std::string label = std::vformat(fmt.get(), std::make_format_args(args...)); return add_right_label(color, offset, outlined,
std::string_view{std::vformat(fmt.get(), std::make_format_args(args...))});
add_top_label(color, offset, outlined, std::string_view{label});
} }
void add_top_label(const Color& color, float offset, bool outlined, std::string_view text);
void add_top_bar(const Color& color, const Color& outline_color, const Color& bg_color, float height,
float ratio, float offset = 5.f);
void add_snap_line(const Vector2<float>& start_pos, const Color& color, float width);
void add_dashed_box(const Color& color, float dash_len = 8.f, float gap_len = 5.f,
float thickness = 1.f) const;
void add_skeleton(const Color& color, float thickness = 1.f) const;
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);
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);
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);
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);
void add_bottom_bar(const Color& color, const Color& outline_color, const Color& bg_color, float height,
float ratio, float offset = 5.f);
template<typename... Args> template<typename... Args>
void add_bottom_label(const Color& color, const float offset, const bool outlined, EntityOverlay& add_left_label(const Color& color, float offset, bool outlined, std::format_string<Args...> fmt,
std::format_string<Args...> fmt, Args&&... args) Args&&... args)
{ {
const std::string label = std::vformat(fmt.get(), std::make_format_args(args...)); return add_left_label(color, offset, outlined,
add_bottom_label(color, offset, outlined, std::string_view{label}); 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<typename... Args> template<typename... Args>
void add_left_label(const Color& color, const float offset, const bool outlined, EntityOverlay& add_top_label(const Color& color, float offset, bool outlined, std::format_string<Args...> fmt,
std::format_string<Args...> fmt, Args&&... args) Args&&... args)
{ {
const std::string label = std::vformat(fmt.get(), std::make_format_args(args...)); return add_top_label(color, offset, outlined,
add_left_label(color, offset, outlined, std::string_view{label}); 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<typename... Args> template<typename... Args>
void add_centered_bottom_label(const Color& color, const float offset, const bool outlined, EntityOverlay& add_bottom_label(const Color& color, float offset, bool outlined,
std::format_string<Args...> fmt, Args&&... args) std::format_string<Args...> fmt, Args&&... args)
{ {
const std::string label = std::vformat(fmt.get(), std::make_format_args(args...)); return add_bottom_label(color, offset, outlined,
add_centered_bottom_label(color, offset, outlined, std::string_view{label}); 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<typename... Args> template<typename... Args>
void add_centered_top_label(const Color& color, const float offset, const bool outlined, EntityOverlay& add_centered_top_label(const Color& color, float offset, bool outlined,
std::format_string<Args...> fmt, Args&&... args) std::format_string<Args...> fmt, Args&&... args)
{ {
const std::string label = std::vformat(fmt.get(), std::make_format_args(args...)); return add_centered_top_label(color, offset, outlined,
add_centered_top_label(color, offset, outlined, std::string_view{label}); 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); template<typename... Args>
EntityOverlay& add_centered_bottom_label(const Color& color, float offset, bool outlined,
std::format_string<Args...> fmt, Args&&... args)
{
return add_centered_bottom_label(color, offset, outlined,
std::string_view{std::vformat(fmt.get(), std::make_format_args(args...))});
}
// ── Misc ─────────────────────────────────────────────────────────
EntityOverlay& add_snap_line(const Vector2<float>& 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<W>
/// from when()) to render them all in declaration order.
template<typename... Widgets>
EntityOverlay& contents(Widgets&&... widgets)
{
(dispatch(std::forward<Widgets>(widgets)), ...);
return *this;
}
private: private:
void draw_outlined_text(const Vector2<float>& position, const Color& color, // optional<W> dispatch — enables when() conditional widgets
const std::string_view& text); template<typename W>
void draw_dashed_line(const Vector2<float>& from, const Vector2<float>& to, const Color& color, void dispatch(const std::optional<W>& w)
float dash_len, float gap_len, float thickness) const; {
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<float>& position, const Color& color, const std::string_view& text);
void draw_dashed_line(const Vector2<float>& from, const Vector2<float>& to, const Color& color, float dash_len,
float gap_len, float thickness) const;
void draw_dashed_fill(const Vector2<float>& origin, const Vector2<float>& step_dir, void draw_dashed_fill(const Vector2<float>& origin, const Vector2<float>& step_dir,
const Vector2<float>& perp_dir, float full_len, float filled_len, const Vector2<float>& perp_dir, float full_len, float filled_len, const Color& fill_color,
const Color& fill_color, const Color& split_color, const Color& split_color, float dash_len, float gap_len) const;
float dash_len, float gap_len) const;
CanvasBox m_canvas; CanvasBox m_canvas;
Vector2<float> m_text_cursor_right; Vector2<float> m_text_cursor_right;
Vector2<float> m_text_cursor_top; Vector2<float> m_text_cursor_top;

View File

@@ -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 <initializer_list>
#include <optional>
#include <string_view>
#include <variant>
namespace omath::hud::widget
{
// ── Overloaded helper for std::visit ──────────────────────────────────────
template<typename... Ts>
struct Overloaded : Ts...
{
using Ts::operator()...;
};
template<typename... Ts>
Overloaded(Ts...) -> Overloaded<Ts...>;
// ── 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<float> 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<typename W>
struct Centered
{
W child;
};
template<typename W>
Centered(W) -> Centered<W>;
// ── Side widget variant ───────────────────────────────────────────────────
struct None {}; ///< No-op placeholder — used by widget::when for disabled elements.
using SideWidget = std::variant<None, Bar, DashedBar, Label, Centered<Label>>;
// ── Side containers ───────────────────────────────────────────────────────
// Storing std::initializer_list<SideWidget> 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<SideWidget> children; RightSide(std::initializer_list<SideWidget> c) : children(c) {} };
struct LeftSide { std::initializer_list<SideWidget> children; LeftSide(std::initializer_list<SideWidget> c) : children(c) {} };
struct TopSide { std::initializer_list<SideWidget> children; TopSide(std::initializer_list<SideWidget> c) : children(c) {} };
struct BottomSide { std::initializer_list<SideWidget> children; BottomSide(std::initializer_list<SideWidget> 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<typename W>
requires std::constructible_from<SideWidget, W>
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<W> when condition is true, std::nullopt otherwise.
/// Designed for use with EntityOverlay::contents() for top-level widget types.
template<typename W>
std::optional<W> when(const bool condition, W widget)
{
if (condition) return std::move(widget);
return std::nullopt;
}
} // namespace omath::hud

View File

@@ -5,7 +5,7 @@
namespace omath::hud 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(); const auto points = m_canvas.as_array();
@@ -13,9 +13,11 @@ namespace omath::hud
if (fill_color.value().w > 0.f) if (fill_color.value().w > 0.f)
m_renderer->add_filled_polyline({points.data(), points.size()}, fill_color); 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, EntityOverlay& EntityOverlay::add_cornered_2d_box(const Color& box_color, const Color& fill_color,
const float corner_ratio_len, const float thickness) const const float corner_ratio_len, const float thickness)
{ {
const auto corner_line_length = const auto corner_line_length =
std::abs((m_canvas.top_left_corner - m_canvas.top_right_corner).x * corner_ratio_len); std::abs((m_canvas.top_left_corner - m_canvas.top_right_corner).x * corner_ratio_len);
@@ -50,8 +52,10 @@ namespace omath::hud
m_renderer->add_line(m_canvas.bottom_right_corner, m_renderer->add_line(m_canvas.bottom_right_corner,
m_canvas.bottom_right_corner - Vector2<float>{corner_line_length, 0.f}, box_color, m_canvas.bottom_right_corner - Vector2<float>{corner_line_length, 0.f}, box_color,
thickness); thickness);
return *this;
} }
void EntityOverlay::add_right_bar(const Color& color, const Color& outline_color, const Color& bg_color, EntityOverlay& EntityOverlay::add_right_bar(const Color& color, const Color& outline_color, const Color& bg_color,
const float width, float ratio, const float offset) const float width, float ratio, const float offset)
{ {
ratio = std::clamp(ratio, 0.f, 1.f); ratio = std::clamp(ratio, 0.f, 1.f);
@@ -65,8 +69,10 @@ namespace omath::hud
bar_start + Vector2<float>(width, -max_bar_height), outline_color); bar_start + Vector2<float>(width, -max_bar_height), outline_color);
m_text_cursor_right.x += offset + width; 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, EntityOverlay& EntityOverlay::add_left_bar(const Color& color, const Color& outline_color, const Color& bg_color,
const float width, float ratio, const float offset) const float width, float ratio, const float offset)
{ {
ratio = std::clamp(ratio, 0.f, 1.f); ratio = std::clamp(ratio, 0.f, 1.f);
@@ -80,8 +86,10 @@ namespace omath::hud
bar_start + Vector2<float>(width, -max_bar_height), outline_color); bar_start + Vector2<float>(width, -max_bar_height), outline_color);
m_text_cursor_left.x -= offset + width; m_text_cursor_left.x -= offset + width;
return *this;
} }
void EntityOverlay::add_right_label(const Color& color, const float offset, const bool outlined, EntityOverlay& EntityOverlay::add_right_label(const Color& color, const float offset, const bool outlined,
const std::string_view& text) const std::string_view& text)
{ {
if (outlined) if (outlined)
@@ -90,8 +98,10 @@ namespace omath::hud
m_renderer->add_text(m_text_cursor_right + Vector2<float>{offset, 0.f}, color, text.data()); m_renderer->add_text(m_text_cursor_right + Vector2<float>{offset, 0.f}, color, text.data());
m_text_cursor_right.y += m_renderer->calc_text_size(text.data()).y; 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, EntityOverlay& EntityOverlay::add_top_label(const Color& color, const float offset, const bool outlined,
const std::string_view text) const std::string_view text)
{ {
m_text_cursor_top.y -= m_renderer->calc_text_size(text.data()).y; m_text_cursor_top.y -= m_renderer->calc_text_size(text.data()).y;
@@ -100,8 +110,10 @@ namespace omath::hud
draw_outlined_text(m_text_cursor_top + Vector2<float>{0.f, -offset}, color, text); draw_outlined_text(m_text_cursor_top + Vector2<float>{0.f, -offset}, color, text);
else else
m_renderer->add_text(m_text_cursor_top + Vector2<float>{0.f, -offset}, color, text.data()); m_renderer->add_text(m_text_cursor_top + Vector2<float>{0.f, -offset}, color, text.data());
return *this;
} }
void EntityOverlay::add_top_bar(const Color& color, const Color& outline_color, const Color& bg_color, EntityOverlay& EntityOverlay::add_top_bar(const Color& color, const Color& outline_color, const Color& bg_color,
const float height, float ratio, const float offset) const float height, float ratio, const float offset)
{ {
ratio = std::clamp(ratio, 0.f, 1.f); ratio = std::clamp(ratio, 0.f, 1.f);
@@ -114,18 +126,21 @@ namespace omath::hud
m_renderer->add_rectangle(bar_start, bar_start + Vector2<float>(max_bar_width, -height), outline_color); m_renderer->add_rectangle(bar_start, bar_start + Vector2<float>(max_bar_width, -height), outline_color);
m_text_cursor_top.y -= offset + height; m_text_cursor_top.y -= offset + height;
return *this;
} }
void EntityOverlay::add_snap_line(const Vector2<float>& start_pos, const Color& color, const float width) EntityOverlay& EntityOverlay::add_snap_line(const Vector2<float>& start_pos, const Color& color, const float width)
{ {
const Vector2<float> line_end = const Vector2<float> line_end =
m_canvas.bottom_left_corner m_canvas.bottom_left_corner
+ Vector2<float>{m_canvas.bottom_right_corner.x - m_canvas.bottom_left_corner.x, 0.f} / 2; + Vector2<float>{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); m_renderer->add_line(start_pos, line_end, color, width);
return *this;
} }
void EntityOverlay::draw_dashed_fill(const Vector2<float>& origin, const Vector2<float>& step_dir, void EntityOverlay::draw_dashed_fill(const Vector2<float>& origin, const Vector2<float>& step_dir,
const Vector2<float>& perp_dir, const float full_len, const Vector2<float>& perp_dir, const float full_len, const float filled_len,
const float filled_len, const Color& fill_color, const Color& fill_color, const Color& split_color, const float dash_len,
const Color& split_color, const float dash_len,
const float gap_len) const const float gap_len) const
{ {
if (full_len <= 0.f) if (full_len <= 0.f)
@@ -141,8 +156,7 @@ namespace omath::hud
const auto fill_rect = [&](const Vector2<float>& a, const Vector2<float>& b, const Color& c) const auto fill_rect = [&](const Vector2<float>& a, const Vector2<float>& b, const Color& c)
{ {
m_renderer->add_filled_rectangle( m_renderer->add_filled_rectangle({std::min(a.x, b.x), std::min(a.y, b.y)},
{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); {std::max(a.x, b.x), std::max(a.y, b.y)}, c);
}; };
@@ -181,77 +195,84 @@ namespace omath::hud
fill_rect(origin + step_dir * trail_start, origin + step_dir * full_len + perp_dir, split_color); 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, EntityOverlay& EntityOverlay::add_right_dashed_bar(const Color& color, const Color& outline_color,
const float width, float ratio, const float dash_len, const Color& bg_color, const float width, float ratio,
const float gap_len, const float offset) const float dash_len, const float gap_len, const float offset)
{ {
ratio = std::clamp(ratio, 0.f, 1.f); 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 float height = std::abs(m_canvas.top_right_corner.y - m_canvas.bottom_right_corner.y);
const auto bar_start = Vector2<float>{m_text_cursor_right.x + offset, m_canvas.bottom_right_corner.y}; const auto bar_start = Vector2<float>{m_text_cursor_right.x + offset, m_canvas.bottom_right_corner.y};
m_renderer->add_filled_rectangle(bar_start, bar_start + Vector2<float>{width, -height}, bg_color); m_renderer->add_filled_rectangle(bar_start, bar_start + Vector2<float>{width, -height}, bg_color);
draw_dashed_fill(bar_start, {0.f, -1.f}, {width, 0.f}, height, height * ratio, draw_dashed_fill(bar_start, {0.f, -1.f}, {width, 0.f}, height, height * ratio, color, outline_color, dash_len,
color, outline_color, dash_len, gap_len); gap_len);
m_renderer->add_rectangle(bar_start - Vector2<float>{1.f, 0.f}, m_renderer->add_rectangle(bar_start - Vector2<float>{1.f, 0.f}, bar_start + Vector2<float>{width, -height},
bar_start + Vector2<float>{width, -height}, outline_color); outline_color);
m_text_cursor_right.x += offset + width; 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, EntityOverlay& EntityOverlay::add_left_dashed_bar(const Color& color, const Color& outline_color,
const float width, float ratio, const float dash_len, const Color& bg_color, const float width, float ratio,
const float gap_len, const float offset) const float dash_len, const float gap_len, const float offset)
{ {
ratio = std::clamp(ratio, 0.f, 1.f); 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 float height = std::abs(m_canvas.top_left_corner.y - m_canvas.bottom_left_corner.y);
const auto bar_start = Vector2<float>{m_text_cursor_left.x - (offset + width), m_canvas.bottom_left_corner.y}; const auto bar_start = Vector2<float>{m_text_cursor_left.x - (offset + width), m_canvas.bottom_left_corner.y};
m_renderer->add_filled_rectangle(bar_start, bar_start + Vector2<float>{width, -height}, bg_color); m_renderer->add_filled_rectangle(bar_start, bar_start + Vector2<float>{width, -height}, bg_color);
draw_dashed_fill(bar_start, {0.f, -1.f}, {width, 0.f}, height, height * ratio, draw_dashed_fill(bar_start, {0.f, -1.f}, {width, 0.f}, height, height * ratio, color, outline_color, dash_len,
color, outline_color, dash_len, gap_len); gap_len);
m_renderer->add_rectangle(bar_start - Vector2<float>{1.f, 0.f}, m_renderer->add_rectangle(bar_start - Vector2<float>{1.f, 0.f}, bar_start + Vector2<float>{width, -height},
bar_start + Vector2<float>{width, -height}, outline_color); outline_color);
m_text_cursor_left.x -= offset + width; 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, EntityOverlay& EntityOverlay::add_top_dashed_bar(const Color& color, const Color& outline_color,
const float height, float ratio, const float dash_len, const Color& bg_color, const float height, float ratio,
const float gap_len, const float offset) const float dash_len, const float gap_len, const float offset)
{ {
ratio = std::clamp(ratio, 0.f, 1.f); 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 float bar_w = std::abs(m_canvas.top_left_corner.x - m_canvas.top_right_corner.x);
const auto bar_start = Vector2<float>{m_canvas.top_left_corner.x, m_text_cursor_top.y - offset}; const auto bar_start = Vector2<float>{m_canvas.top_left_corner.x, m_text_cursor_top.y - offset};
m_renderer->add_filled_rectangle(bar_start, bar_start + Vector2<float>{bar_w, -height}, bg_color); m_renderer->add_filled_rectangle(bar_start, bar_start + Vector2<float>{bar_w, -height}, bg_color);
draw_dashed_fill(bar_start, {1.f, 0.f}, {0.f, -height}, bar_w, bar_w * ratio, draw_dashed_fill(bar_start, {1.f, 0.f}, {0.f, -height}, bar_w, bar_w * ratio, color, outline_color, dash_len,
color, outline_color, dash_len, gap_len); gap_len);
m_renderer->add_rectangle(bar_start, bar_start + Vector2<float>{bar_w, -height}, outline_color); m_renderer->add_rectangle(bar_start, bar_start + Vector2<float>{bar_w, -height}, outline_color);
m_text_cursor_top.y -= offset + height; 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, EntityOverlay& EntityOverlay::add_bottom_dashed_bar(const Color& color, const Color& outline_color,
const float height, float ratio, const float dash_len, const Color& bg_color, const float height, float ratio,
const float gap_len, const float offset) const float dash_len, const float gap_len, const float offset)
{ {
ratio = std::clamp(ratio, 0.f, 1.f); 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 float bar_w = std::abs(m_canvas.bottom_left_corner.x - m_canvas.bottom_right_corner.x);
const auto bar_start = Vector2<float>{m_canvas.bottom_left_corner.x, m_text_cursor_bottom.y + offset}; const auto bar_start = Vector2<float>{m_canvas.bottom_left_corner.x, m_text_cursor_bottom.y + offset};
m_renderer->add_filled_rectangle(bar_start, bar_start + Vector2<float>{bar_w, height}, bg_color); m_renderer->add_filled_rectangle(bar_start, bar_start + Vector2<float>{bar_w, height}, bg_color);
draw_dashed_fill(bar_start, {1.f, 0.f}, {0.f, height}, bar_w, bar_w * ratio, draw_dashed_fill(bar_start, {1.f, 0.f}, {0.f, height}, bar_w, bar_w * ratio, color, outline_color, dash_len,
color, outline_color, dash_len, gap_len); gap_len);
m_renderer->add_rectangle(bar_start, bar_start + Vector2<float>{bar_w, height}, outline_color); m_renderer->add_rectangle(bar_start, bar_start + Vector2<float>{bar_w, height}, outline_color);
m_text_cursor_bottom.y += offset + height; 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 // Maps normalized (rx in [0,1], ry in [0,1]) to canvas screen position
const auto joint = [&](const float rx, const float ry) -> Vector2<float> const auto joint = [&](const float rx, const float ry) -> Vector2<float>
{ {
const auto top = m_canvas.top_left_corner const auto top = m_canvas.top_left_corner + (m_canvas.top_right_corner - m_canvas.top_left_corner) * rx;
+ (m_canvas.top_right_corner - m_canvas.top_left_corner) * rx; const auto bot =
const auto bot = m_canvas.bottom_left_corner m_canvas.bottom_left_corner + (m_canvas.bottom_right_corner - m_canvas.bottom_left_corner) * rx;
+ (m_canvas.bottom_right_corner - m_canvas.bottom_left_corner) * rx;
return top + (bot - top) * ry; return top + (bot - top) * ry;
}; };
@@ -281,6 +302,8 @@ namespace omath::hud
for (const auto& [a, b] : k_bones) for (const auto& [a, b] : k_bones)
m_renderer->add_line(joint(a.first, a.second), joint(b.first, b.second), color, thickness); 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<float>& from, const Vector2<float>& to, const Color& color, void EntityOverlay::draw_dashed_line(const Vector2<float>& from, const Vector2<float>& to, const Color& color,
@@ -309,11 +332,10 @@ namespace omath::hud
} }
} }
void EntityOverlay::add_dashed_box(const Color& color, const float dash_len, const float gap_len, EntityOverlay& EntityOverlay::add_dashed_box(const Color& color, const float dash_len, const float gap_len,
const float thickness) const const float thickness)
{ {
const float min_edge = std::min( const float min_edge = std::min((m_canvas.top_right_corner - m_canvas.top_left_corner).length(),
(m_canvas.top_right_corner - m_canvas.top_left_corner).length(),
(m_canvas.bottom_right_corner - m_canvas.top_right_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 float corner_len = std::min(dash_len, min_edge / 2.f);
@@ -330,6 +352,8 @@ namespace omath::hud
draw_edge(m_canvas.top_right_corner, m_canvas.bottom_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_right_corner, m_canvas.bottom_left_corner);
draw_edge(m_canvas.bottom_left_corner, m_canvas.top_left_corner); draw_edge(m_canvas.bottom_left_corner, m_canvas.top_left_corner);
return *this;
} }
void EntityOverlay::draw_outlined_text(const Vector2<float>& position, const Color& color, void EntityOverlay::draw_outlined_text(const Vector2<float>& position, const Color& color,
@@ -343,7 +367,7 @@ 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 + outline_offset, Color{0.f, 0.f, 0.f, 1.f}, text.data());
m_renderer->add_text(position, color, 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, EntityOverlay& EntityOverlay::add_bottom_bar(const Color& color, const Color& outline_color, const Color& bg_color,
const float height, float ratio, const float offset) const float height, float ratio, const float offset)
{ {
ratio = std::clamp(ratio, 0.f, 1.f); ratio = std::clamp(ratio, 0.f, 1.f);
@@ -355,9 +379,11 @@ namespace omath::hud
m_renderer->add_rectangle(bar_start, bar_start + Vector2<float>(max_bar_width, height), outline_color); m_renderer->add_rectangle(bar_start, bar_start + Vector2<float>(max_bar_width, height), outline_color);
m_text_cursor_bottom.y += offset + height; m_text_cursor_bottom.y += offset + height;
return *this;
} }
void EntityOverlay::add_bottom_label(const Color& color, const float offset, const bool outlined, EntityOverlay& EntityOverlay::add_bottom_label(const Color& color, const float offset, const bool outlined,
const std::string_view text) const std::string_view text)
{ {
const auto text_size = m_renderer->calc_text_size(text); const auto text_size = m_renderer->calc_text_size(text);
@@ -368,9 +394,11 @@ namespace omath::hud
m_renderer->add_text(m_text_cursor_bottom + Vector2<float>{0.f, offset}, color, text); m_renderer->add_text(m_text_cursor_bottom + Vector2<float>{0.f, offset}, color, text);
m_text_cursor_bottom.y += text_size.y; m_text_cursor_bottom.y += text_size.y;
return *this;
} }
void EntityOverlay::add_left_label(const Color& color, const float offset, const bool outlined, EntityOverlay& EntityOverlay::add_left_label(const Color& color, const float offset, const bool outlined,
const std::string_view& text) const std::string_view& text)
{ {
const auto text_size = m_renderer->calc_text_size(text); const auto text_size = m_renderer->calc_text_size(text);
@@ -382,9 +410,11 @@ namespace omath::hud
m_renderer->add_text(pos, color, text); m_renderer->add_text(pos, color, text);
m_text_cursor_left.y += text_size.y; 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, EntityOverlay& EntityOverlay::add_centered_bottom_label(const Color& color, const float offset, const bool outlined,
const std::string_view& text) const std::string_view& text)
{ {
const auto text_size = m_renderer->calc_text_size(text); const auto text_size = m_renderer->calc_text_size(text);
@@ -398,9 +428,11 @@ namespace omath::hud
m_renderer->add_text(pos, color, text); m_renderer->add_text(pos, color, text);
m_text_cursor_bottom.y += text_size.y; 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, EntityOverlay& EntityOverlay::add_centered_top_label(const Color& color, const float offset, const bool outlined,
const std::string_view& text) const std::string_view& text)
{ {
const auto text_size = m_renderer->calc_text_size(text); const auto text_size = m_renderer->calc_text_size(text);
@@ -414,6 +446,8 @@ namespace omath::hud
draw_outlined_text(pos, color, text); draw_outlined_text(pos, color, text);
else else
m_renderer->add_text(pos, color, text); m_renderer->add_text(pos, color, text);
return *this;
} }
EntityOverlay::EntityOverlay(const Vector2<float>& top, const Vector2<float>& bottom, EntityOverlay::EntityOverlay(const Vector2<float>& top, const Vector2<float>& bottom,
@@ -423,4 +457,85 @@ namespace omath::hud
m_text_cursor_left(m_canvas.top_left_corner), m_renderer(renderer) 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<widget::Label>& 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<widget::Label>& 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<widget::Label>& 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<widget::Label>& w)
{ add_centered_bottom_label(w.child.color, w.child.offset, w.child.outlined, w.child.text); },
}, child);
}
} // namespace omath::hud } // namespace omath::hud