From 5f1ff2763168f29fd393978e5b70fe1bfdf73324 Mon Sep 17 00:00:00 2001 From: Robertleoj Date: Sun, 14 Sep 2025 18:23:35 +0200 Subject: [PATCH 1/6] first prototype --- slamd/include/slamd_window/tree/tree.hpp | 2 +- .../include/slamd_window/view/canvas_view.hpp | 3 - .../include/slamd_window/view/scene_view.hpp | 3 - slamd/include/slamd_window/view/view.hpp | 2 + slamd/src/window/run_window.cpp | 127 ++++++++++++++++++ slamd/src/window/view/canvas_view.cpp | 2 +- slamd/src/window/view/scene_view.cpp | 2 +- slamd/src/window/view/view.cpp | 5 + 8 files changed, 137 insertions(+), 9 deletions(-) diff --git a/slamd/include/slamd_window/tree/tree.hpp b/slamd/include/slamd_window/tree/tree.hpp index 6e515ac2..1e1d8668 100644 --- a/slamd/include/slamd_window/tree/tree.hpp +++ b/slamd/include/slamd_window/tree/tree.hpp @@ -11,11 +11,11 @@ namespace slamd { class Tree { private: uint64_t id; - std::unique_ptr root; public: Tree(uint64_t id); Tree(uint64_t id, std::unique_ptr&& root); + std::unique_ptr root; virtual void set_object(const TreePath& path, std::shared_ptr<_geom::Geometry> object); diff --git a/slamd/include/slamd_window/view/canvas_view.hpp b/slamd/include/slamd_window/view/canvas_view.hpp index 10d80459..cc83462d 100644 --- a/slamd/include/slamd_window/view/canvas_view.hpp +++ b/slamd/include/slamd_window/view/canvas_view.hpp @@ -9,9 +9,6 @@ namespace slamd { class CanvasView : public View { - public: - std::shared_ptr tree; - private: FrameBuffer frame_buffer; Camera2D camera; diff --git a/slamd/include/slamd_window/view/scene_view.hpp b/slamd/include/slamd_window/view/scene_view.hpp index c39b27de..6031e8a9 100644 --- a/slamd/include/slamd_window/view/scene_view.hpp +++ b/slamd/include/slamd_window/view/scene_view.hpp @@ -10,9 +10,6 @@ namespace slamd { class SceneView : public View { - public: - std::shared_ptr tree; - private: FrameBuffer frame_buffer; Arcball arcball; diff --git a/slamd/include/slamd_window/view/view.hpp b/slamd/include/slamd_window/view/view.hpp index 117f69fb..daa9282f 100644 --- a/slamd/include/slamd_window/view/view.hpp +++ b/slamd/include/slamd_window/view/view.hpp @@ -6,10 +6,12 @@ namespace slamd { class View { public: + View(std::shared_ptr t); virtual void render_to_imgui() = 0; // pure virtual = abstract base class virtual ~View() = default; // virtual dtor for safe polymorphic deletion static std::unique_ptr deserialize(const flatb::View* view, std::shared_ptr tree); + std::shared_ptr tree; }; } // namespace slamd \ No newline at end of file diff --git a/slamd/src/window/run_window.cpp b/slamd/src/window/run_window.cpp index de2a9fa3..62330f42 100644 --- a/slamd/src/window/run_window.cpp +++ b/slamd/src/window/run_window.cpp @@ -18,6 +18,130 @@ void framebuffer_size_callback( gl::glViewport(0, 0, width, height); } +inline void tree_menu( + Node* root, + ImGuiTreeNodeFlags node_flags = 0 +) { + static char filter_buf[128] = ""; // text input buffer (persists) + + // Compute a field width that fully shows the text (plus padding) + const float min_field_w = 250.0f; + const float text_w = ImGui::CalcTextSize(filter_buf).x; + const float pad_x = ImGui::GetStyle().FramePadding.x; + + // A little extra so the caret isn’t jammed at the edge + const float desired_field_w = text_w + 2.0f * pad_x + 12.0f; + const float field_w = + (desired_field_w < min_field_w) ? min_field_w : desired_field_w; + + ImGui::AlignTextToFramePadding(); + ImGui::TextUnformatted("Filter:"); + ImGui::SameLine(); + ImGui::SetNextItemWidth(field_w); // take all remaining width on the line + ImGui::InputText("##filter", filter_buf, IM_ARRAYSIZE(filter_buf)); + ImGui::Separator(); + + // --- Text input box above tree --- + // ImGui::InputText("Filter:##filter", filter_buf, + // IM_ARRAYSIZE(filter_buf)); ImGui::Separator(); + + std::function draw_node = + [&](Node* n, std::string label, int depth) { + ImGui::PushID(n); // stable-ish ID; swap for n->id if you got one + + const bool has_children = !n->children.empty(); + const ImGuiTreeNodeFlags base_flags = + ImGuiTreeNodeFlags_SpanAvailWidth | + ImGuiTreeNodeFlags_FramePadding | + (has_children ? 0 + : ImGuiTreeNodeFlags_Leaf | + ImGuiTreeNodeFlags_NoTreePushOnOpen); + + // Checkbox aligned to the right of the same line + bool visible = true; + ImGui::Checkbox("##visible", &visible); + ImGui::SameLine(0.0f, 4.0f); + + bool open = false; + if (has_children) { + open = + ImGui::TreeNodeEx(label.c_str(), base_flags | node_flags); + } else { + ImGui::TreeNodeEx(label.c_str(), base_flags | node_flags); + } + + // Optional context menu + if (ImGui::BeginPopupContextItem("tree_ctx")) { + ImGui::TextUnformatted(label.c_str()); + ImGui::Separator(); + if (ImGui::MenuItem("Select")) { + } + ImGui::EndPopup(); + } + + if (open) { + for (const auto& c : n->children) { + draw_node(c.second.get(), c.first, depth + 1); + } + ImGui::TreePop(); + } + ImGui::PopID(); + }; + + draw_node(root, "/", 0); +} + +inline void draw_tree_overlay( + Node* root, + const char* overlay_id = "##scene_tree_overlay", + const char* header = "Tree", + float margin = 8.0f, + float min_width = 100.0f +) { + // Anchor to current window's content region (screen coords) + ImVec2 win_pos = ImGui::GetWindowPos(); + ImVec2 cr_min = ImGui::GetWindowContentRegionMin(); + ImVec2 cr_max = ImGui::GetWindowContentRegionMax(); + ImVec2 tl(win_pos.x + cr_min.x, win_pos.y + cr_min.y); + ImVec2 br(win_pos.x + cr_max.x, win_pos.y + cr_max.y); + + ImVec2 pos(tl.x + margin, tl.y + margin); + ImVec2 max_size(br.x - tl.x - 2 * margin, br.y - tl.y - 2 * margin); + + // Style: dark translucent bg, rounded corners, slim padding + ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 8.0f); + ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 1.0f); + ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(6, 6)); + ImGui::PushStyleColor(ImGuiCol_WindowBg, ImVec4(0, 0, 0, 0.55f)); + + ImGui::SetNextWindowPos(pos, ImGuiCond_Always, ImVec2(0, 0)); + ImGui::SetNextWindowViewport(ImGui::GetWindowViewport()->ID); + ImGui::SetNextWindowSizeConstraints(ImVec2(min_width, 0), max_size); + + ImGuiWindowFlags flags = + ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_AlwaysAutoResize | + ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_NoFocusOnAppearing | + ImGuiWindowFlags_NoNav | ImGuiWindowFlags_NoMove; // pinned + + ImGui::Begin(overlay_id, nullptr, flags); + + if (header && *header) { + ImGui::TextUnformatted(header); + ImGui::Separator(); + } + + // Reuse your tree renderer (defaults collapsed) + tree_menu( + root, + ImGuiTreeNodeFlags_OpenOnArrow | ImGuiTreeNodeFlags_SpanFullWidth + ); + + ImGui::End(); + + ImGui::PopStyleColor(); + ImGui::PopStyleVar(3); +} + void run_window( StateManager& state_manager ) { @@ -90,6 +214,9 @@ void run_window( ImGuiWindowFlags_NoScrollWithMouse ); scene->render_to_imgui(); + + draw_tree_overlay(scene->tree->root.get()); + ImGui::End(); ImGui::PopStyleVar(); } diff --git a/slamd/src/window/view/canvas_view.cpp b/slamd/src/window/view/canvas_view.cpp index d51f8626..6f421989 100644 --- a/slamd/src/window/view/canvas_view.cpp +++ b/slamd/src/window/view/canvas_view.cpp @@ -9,7 +9,7 @@ namespace slamd { CanvasView::CanvasView( std::shared_ptr tree ) - : tree(tree), + : View(std::move(tree)), frame_buffer(500, 500), camera(slamd::gmath::Rect2D({0.0, 0.0}, {1.0, 1.0})), manually_moved(false) {} diff --git a/slamd/src/window/view/scene_view.cpp b/slamd/src/window/view/scene_view.cpp index 9cf1a561..5e344813 100644 --- a/slamd/src/window/view/scene_view.cpp +++ b/slamd/src/window/view/scene_view.cpp @@ -16,7 +16,7 @@ glm::vec3 make_background_color( SceneView::SceneView( std::shared_ptr tree ) - : tree(tree), + : View(std::move(tree)), frame_buffer(500, 500), camera(45.0, 0.1f, 100000.0f), xy_grid(1000.0) { diff --git a/slamd/src/window/view/view.cpp b/slamd/src/window/view/view.cpp index 752ecee2..1a22d35b 100644 --- a/slamd/src/window/view/view.cpp +++ b/slamd/src/window/view/view.cpp @@ -4,6 +4,11 @@ namespace slamd { +View::View( + std::shared_ptr t +) + : tree(std::move(t)) {} + std::unique_ptr View::deserialize( const flatb::View* view_fb, std::shared_ptr tree From 573d56eaba9252e7c44c9c197e8629756bdfc72c Mon Sep 17 00:00:00 2001 From: Robertleoj Date: Sun, 14 Sep 2025 19:02:31 +0200 Subject: [PATCH 2/6] improve prototype --- slamd/include/slamd_window/tree/node.hpp | 1 + slamd/src/window/run_window.cpp | 50 +++++++++++------------- 2 files changed, 23 insertions(+), 28 deletions(-) diff --git a/slamd/include/slamd_window/tree/node.hpp b/slamd/include/slamd_window/tree/node.hpp index 9a1bae45..e005518f 100644 --- a/slamd/include/slamd_window/tree/node.hpp +++ b/slamd/include/slamd_window/tree/node.hpp @@ -28,6 +28,7 @@ class Node { mutable std::mutex object_mutex; public: + bool checked = true; std::optional> get_object() const; std::optional get_transform() const; diff --git a/slamd/src/window/run_window.cpp b/slamd/src/window/run_window.cpp index 62330f42..fe601d68 100644 --- a/slamd/src/window/run_window.cpp +++ b/slamd/src/window/run_window.cpp @@ -19,8 +19,7 @@ void framebuffer_size_callback( } inline void tree_menu( - Node* root, - ImGuiTreeNodeFlags node_flags = 0 + Node* root ) { static char filter_buf[128] = ""; // text input buffer (persists) @@ -41,54 +40,52 @@ inline void tree_menu( ImGui::InputText("##filter", filter_buf, IM_ARRAYSIZE(filter_buf)); ImGui::Separator(); - // --- Text input box above tree --- - // ImGui::InputText("Filter:##filter", filter_buf, - // IM_ARRAYSIZE(filter_buf)); ImGui::Separator(); - - std::function draw_node = - [&](Node* n, std::string label, int depth) { + std::function draw_node = + [&](Node* n, std::string label, int depth, bool parent_dimmed) { ImGui::PushID(n); // stable-ish ID; swap for n->id if you got one const bool has_children = !n->children.empty(); const ImGuiTreeNodeFlags base_flags = ImGuiTreeNodeFlags_SpanAvailWidth | + ImGuiTreeNodeFlags_SpanFullWidth | ImGuiTreeNodeFlags_FramePadding | (has_children ? 0 : ImGuiTreeNodeFlags_Leaf | ImGuiTreeNodeFlags_NoTreePushOnOpen); + // Inherit dimming if any ancestor is unchecked, or this node is + // unchecked + const bool dimmed = parent_dimmed || !n->checked; + bool dimmed_here = !parent_dimmed && !n->checked; + if (dimmed_here) { + float base_alpha = ImGui::GetStyle().Alpha; + ImGui::PushStyleVar(ImGuiStyleVar_Alpha, base_alpha * 0.45f); + } + // Checkbox aligned to the right of the same line - bool visible = true; - ImGui::Checkbox("##visible", &visible); + ImGui::Checkbox("##visible", &n->checked); ImGui::SameLine(0.0f, 4.0f); bool open = false; if (has_children) { - open = - ImGui::TreeNodeEx(label.c_str(), base_flags | node_flags); + open = ImGui::TreeNodeEx(label.c_str(), base_flags); } else { - ImGui::TreeNodeEx(label.c_str(), base_flags | node_flags); - } - - // Optional context menu - if (ImGui::BeginPopupContextItem("tree_ctx")) { - ImGui::TextUnformatted(label.c_str()); - ImGui::Separator(); - if (ImGui::MenuItem("Select")) { - } - ImGui::EndPopup(); + ImGui::TreeNodeEx(label.c_str(), base_flags); } if (open) { for (const auto& c : n->children) { - draw_node(c.second.get(), c.first, depth + 1); + draw_node(c.second.get(), c.first, depth + 1, dimmed); } ImGui::TreePop(); } + if (dimmed_here) { + ImGui::PopStyleVar(); + } ImGui::PopID(); }; - draw_node(root, "/", 0); + draw_node(root, "/", 0, false); } inline void draw_tree_overlay( @@ -131,10 +128,7 @@ inline void draw_tree_overlay( } // Reuse your tree renderer (defaults collapsed) - tree_menu( - root, - ImGuiTreeNodeFlags_OpenOnArrow | ImGuiTreeNodeFlags_SpanFullWidth - ); + tree_menu(root); ImGui::End(); From 7372e8120d7fa8d1643136e396da39ae19d2c99b Mon Sep 17 00:00:00 2001 From: Robertleoj Date: Sun, 14 Sep 2025 19:21:37 +0200 Subject: [PATCH 3/6] better UI --- slamd/include/slamd_window/view/view.hpp | 3 ++ slamd/src/window/run_window.cpp | 68 ++++++++++++++++++++---- 2 files changed, 61 insertions(+), 10 deletions(-) diff --git a/slamd/include/slamd_window/view/view.hpp b/slamd/include/slamd_window/view/view.hpp index daa9282f..12ecf207 100644 --- a/slamd/include/slamd_window/view/view.hpp +++ b/slamd/include/slamd_window/view/view.hpp @@ -12,6 +12,9 @@ class View { static std::unique_ptr deserialize(const flatb::View* view, std::shared_ptr tree); std::shared_ptr tree; + + std::optional visualize_glob = std::nullopt; + char filter_buf[512] = ""; }; } // namespace slamd \ No newline at end of file diff --git a/slamd/src/window/run_window.cpp b/slamd/src/window/run_window.cpp index fe601d68..4e4effc3 100644 --- a/slamd/src/window/run_window.cpp +++ b/slamd/src/window/run_window.cpp @@ -19,13 +19,13 @@ void framebuffer_size_callback( } inline void tree_menu( - Node* root + View* view ) { - static char filter_buf[128] = ""; // text input buffer (persists) + Node* root = view->tree->root.get(); // Compute a field width that fully shows the text (plus padding) const float min_field_w = 250.0f; - const float text_w = ImGui::CalcTextSize(filter_buf).x; + const float text_w = ImGui::CalcTextSize(view->filter_buf).x; const float pad_x = ImGui::GetStyle().FramePadding.x; // A little extra so the caret isn’t jammed at the edge @@ -33,16 +33,66 @@ inline void tree_menu( const float field_w = (desired_field_w < min_field_w) ? min_field_w : desired_field_w; + std::optional filter_error_text; + std::optional filter_path; + std::string filter_string(view->filter_buf); + + if (filter_string.size() > 0) { + try { + filter_path = TreePath(filter_string); + } catch (const std::invalid_argument& e) { + filter_error_text = e.what(); + } + } + ImGui::AlignTextToFramePadding(); ImGui::TextUnformatted("Filter:"); ImGui::SameLine(); ImGui::SetNextItemWidth(field_w); // take all remaining width on the line - ImGui::InputText("##filter", filter_buf, IM_ARRAYSIZE(filter_buf)); + + if (filter_error_text.has_value()) { + ImGui::PushStyleColor( + ImGuiCol_FrameBg, + ImVec4(0.35f, 0.10f, 0.10f, 1.0f) + ); + ImGui::PushStyleColor( + ImGuiCol_FrameBgHovered, + ImVec4(0.45f, 0.12f, 0.12f, 1.0f) + ); + ImGui::PushStyleColor( + ImGuiCol_FrameBgActive, + ImVec4(0.50f, 0.13f, 0.13f, 1.0f) + ); + ImGui::PushStyleColor( + ImGuiCol_Border, + ImVec4(0.90f, 0.20f, 0.20f, 1.0f) + ); + ImGui::PushStyleVar(ImGuiStyleVar_FrameBorderSize, 1.0f); + } + ImGui::InputText( + "##filter", + view->filter_buf, + IM_ARRAYSIZE(view->filter_buf) + ); + if (filter_error_text.has_value()) { + ImGui::PopStyleVar(); + ImGui::PopStyleColor(4); + + // Tooltip on hover + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("%s", filter_error_text.value().c_str()); + } + + // Inline red "!" marker + ImGui::SameLine(); + ImGui::TextColored(ImVec4(0.95f, 0.25f, 0.25f, 1.0f), "!"); + } + ImGui::Separator(); std::function draw_node = [&](Node* n, std::string label, int depth, bool parent_dimmed) { - ImGui::PushID(n); // stable-ish ID; swap for n->id if you got one + ImGui::PushID(n); const bool has_children = !n->children.empty(); const ImGuiTreeNodeFlags base_flags = @@ -53,8 +103,6 @@ inline void tree_menu( : ImGuiTreeNodeFlags_Leaf | ImGuiTreeNodeFlags_NoTreePushOnOpen); - // Inherit dimming if any ancestor is unchecked, or this node is - // unchecked const bool dimmed = parent_dimmed || !n->checked; bool dimmed_here = !parent_dimmed && !n->checked; if (dimmed_here) { @@ -89,7 +137,7 @@ inline void tree_menu( } inline void draw_tree_overlay( - Node* root, + View* view, const char* overlay_id = "##scene_tree_overlay", const char* header = "Tree", float margin = 8.0f, @@ -128,7 +176,7 @@ inline void draw_tree_overlay( } // Reuse your tree renderer (defaults collapsed) - tree_menu(root); + tree_menu(view); ImGui::End(); @@ -209,7 +257,7 @@ void run_window( ); scene->render_to_imgui(); - draw_tree_overlay(scene->tree->root.get()); + draw_tree_overlay(scene.get()); ImGui::End(); ImGui::PopStyleVar(); From 0df317f7f627fe16ba0691c2586dc248bf973a49 Mon Sep 17 00:00:00 2001 From: Robertleoj Date: Sun, 14 Sep 2025 19:34:43 +0200 Subject: [PATCH 4/6] filters work --- slamd/include/slamd_window/tree/node.hpp | 2 + slamd/include/slamd_window/tree/tree_path.hpp | 1 + slamd/src/window/run_window.cpp | 27 ++++++-- slamd/src/window/tree/tree.cpp | 6 +- slamd/src/window/tree/tree_path.cpp | 62 +++++++++++++++++++ 5 files changed, 93 insertions(+), 5 deletions(-) diff --git a/slamd/include/slamd_window/tree/node.hpp b/slamd/include/slamd_window/tree/node.hpp index e005518f..29ceb82c 100644 --- a/slamd/include/slamd_window/tree/node.hpp +++ b/slamd/include/slamd_window/tree/node.hpp @@ -29,6 +29,8 @@ class Node { public: bool checked = true; + std::optional glob_matches = std::nullopt; + std::optional> get_object() const; std::optional get_transform() const; diff --git a/slamd/include/slamd_window/tree/tree_path.hpp b/slamd/include/slamd_window/tree/tree_path.hpp index 5c707678..005da047 100644 --- a/slamd/include/slamd_window/tree/tree_path.hpp +++ b/slamd/include/slamd_window/tree/tree_path.hpp @@ -13,6 +13,7 @@ class TreePath { TreePath parent() const; std::string string() const; TreePath& operator=(const TreePath&) = default; + bool matches_glob(const TreePath& glob_path); public: std::vector components; diff --git a/slamd/src/window/run_window.cpp b/slamd/src/window/run_window.cpp index 4e4effc3..15bf04cb 100644 --- a/slamd/src/window/run_window.cpp +++ b/slamd/src/window/run_window.cpp @@ -90,10 +90,20 @@ inline void tree_menu( ImGui::Separator(); - std::function draw_node = - [&](Node* n, std::string label, int depth, bool parent_dimmed) { + std::function draw_node = + [&](Node* n, + std::string label, + int depth, + bool parent_dimmed, + TreePath& full_path) { ImGui::PushID(n); + if (filter_path.has_value()) { + n->glob_matches = full_path.matches_glob(filter_path.value()); + } else { + n->glob_matches = std::nullopt; + } + const bool has_children = !n->children.empty(); const ImGuiTreeNodeFlags base_flags = ImGuiTreeNodeFlags_SpanAvailWidth | @@ -123,7 +133,15 @@ inline void tree_menu( if (open) { for (const auto& c : n->children) { - draw_node(c.second.get(), c.first, depth + 1, dimmed); + full_path.components.push_back(c.first); + draw_node( + c.second.get(), + c.first, + depth + 1, + dimmed, + full_path + ); + full_path.components.pop_back(); } ImGui::TreePop(); } @@ -133,7 +151,8 @@ inline void tree_menu( ImGui::PopID(); }; - draw_node(root, "/", 0, false); + TreePath pth("/"); + draw_node(root, "/", 0, false, pth); } inline void draw_tree_overlay( diff --git a/slamd/src/window/tree/tree.cpp b/slamd/src/window/tree/tree.cpp index 3f2af726..c4822356 100644 --- a/slamd/src/window/tree/tree.cpp +++ b/slamd/src/window/tree/tree.cpp @@ -119,6 +119,10 @@ void Tree::render_recursive( const glm::mat4& view, const glm::mat4& projection ) const { + if (!node->glob_matches.has_value() && !node->checked) { + return; + } + glm::mat4 next_transform = current_transform; auto node_transform = node->get_transform(); @@ -128,7 +132,7 @@ void Tree::render_recursive( const auto node_object = node->get_object(); - if (node_object.has_value()) { + if (node_object.has_value() && node->glob_matches.value_or(true)) { node_object.value()->render(next_transform, view, projection); } diff --git a/slamd/src/window/tree/tree_path.cpp b/slamd/src/window/tree/tree_path.cpp index d62a1f34..87830e9d 100644 --- a/slamd/src/window/tree/tree_path.cpp +++ b/slamd/src/window/tree/tree_path.cpp @@ -93,4 +93,66 @@ TreePath operator/( return TreePath(new_components); } +bool match_component( + const std::string& pattern, + const std::string& text +) { + // only * is supported (not ? or []) + size_t pi = 0, ti = 0, star = std::string::npos, match = 0; + while (ti < text.size()) { + if (pi < pattern.size() && + (pattern[pi] == text[ti] || pattern[pi] == '?')) { + ++pi; + ++ti; + } else if (pi < pattern.size() && pattern[pi] == '*') { + star = pi++; + match = ti; + } else if (star != std::string::npos) { + pi = star + 1; + ti = ++match; + } else { + return false; + } + } + while (pi < pattern.size() && pattern[pi] == '*') { + ++pi; + } + return pi == pattern.size(); +} + +bool match_glob( + const std::vector& path, + const std::vector& pattern, + size_t pi = 0, + size_t si = 0 +) { + while (pi < pattern.size()) { + if (pattern[pi] == "**") { + // try to consume any number of segments + for (size_t skip = 0; si + skip <= path.size(); ++skip) { + if (match_glob(path, pattern, pi + 1, si + skip)) { + return true; + } + } + return false; + } else { + if (si >= path.size()) { + return false; + } + if (!match_component(pattern[pi], path[si])) { + return false; + } + ++pi; + ++si; + } + } + return si == path.size(); +} + +bool TreePath::matches_glob( + const TreePath& glob_path +) { + return match_glob(this->components, glob_path.components); +} + } // namespace slamd \ No newline at end of file From c41c5b9f5aa2d8bea86a0ba16574a181db887a85 Mon Sep 17 00:00:00 2001 From: Robertleoj Date: Sun, 14 Sep 2025 19:52:33 +0200 Subject: [PATCH 5/6] works --- slamd/include/slamd_window/tree/tree.hpp | 1 + slamd/src/window/run_window.cpp | 36 ++++++++++++++++++++++++ slamd/src/window/tree/tree.cpp | 25 ++++++++++++++++ 3 files changed, 62 insertions(+) diff --git a/slamd/include/slamd_window/tree/tree.hpp b/slamd/include/slamd_window/tree/tree.hpp index 1e1d8668..f2d0f1f2 100644 --- a/slamd/include/slamd_window/tree/tree.hpp +++ b/slamd/include/slamd_window/tree/tree.hpp @@ -31,6 +31,7 @@ class Tree { std::optional bounds(); void set_transform(const TreePath& path, const glm::mat4& transform); void clear(const TreePath& path); + void mark_nodes_matching_glob(std::optional glob); protected: std::optional traverse(const TreePath& path); diff --git a/slamd/src/window/run_window.cpp b/slamd/src/window/run_window.cpp index 15bf04cb..c26c1745 100644 --- a/slamd/src/window/run_window.cpp +++ b/slamd/src/window/run_window.cpp @@ -45,6 +45,8 @@ inline void tree_menu( } } + view->tree->mark_nodes_matching_glob(filter_path); + ImGui::AlignTextToFramePadding(); ImGui::TextUnformatted("Filter:"); ImGui::SameLine(); @@ -131,6 +133,40 @@ inline void tree_menu( ImGui::TreeNodeEx(label.c_str(), base_flags); } + if (n->glob_matches.has_value()) { + const bool match = n->glob_matches.value_or(false); + + // Get row rect from the last item (the tree node we just drew) + ImVec2 item_min = ImGui::GetItemRectMin(); + ImVec2 item_max = ImGui::GetItemRectMax(); + + // Compute right edge of the content area (screen coords) + ImVec2 win_pos = ImGui::GetWindowPos(); + ImVec2 cr_max = ImGui::GetWindowContentRegionMax(); + float right_edge_x = + win_pos.x + cr_max.x - ImGui::GetStyle().ItemInnerSpacing.x; + + // Circle params + float radius = 4.0f; + float cx = right_edge_x - radius; // right-aligned + float cy = + 0.5f * (item_min.y + item_max.y); // vertically centered + + ImU32 col_fill = + ImGui::GetColorU32(ImVec4(0.30f, 0.85f, 0.40f, 1.0f) + ); // green + ImU32 col_line = ImGui::GetColorU32( + ImGui::GetStyleColorVec4(ImGuiCol_TextDisabled) + ); + + ImDrawList* dl = ImGui::GetWindowDrawList(); + if (match) { + dl->AddCircleFilled(ImVec2(cx, cy), radius, col_fill); + } else { + dl->AddCircle(ImVec2(cx, cy), radius, col_line, 12, 1.5f); + } + } + if (open) { for (const auto& c : n->children) { full_path.components.push_back(c.first); diff --git a/slamd/src/window/tree/tree.cpp b/slamd/src/window/tree/tree.cpp index c4822356..8be99492 100644 --- a/slamd/src/window/tree/tree.cpp +++ b/slamd/src/window/tree/tree.cpp @@ -18,6 +18,31 @@ Tree::Tree( : id(id), root(std::move(root)) {} +void mark_glob_match_recursive( + Node* node, + TreePath& path, + std::optional& glob +) { + if (!glob.has_value()) { + node->glob_matches = std::nullopt; + } else { + node->glob_matches = path.matches_glob(glob.value()); + } + + for (auto& [label, child] : node->children) { + path.components.push_back(label); + mark_glob_match_recursive(child.get(), path, glob); + path.components.pop_back(); + } +} + +void Tree::mark_nodes_matching_glob( + std::optional glob +) { + TreePath pth("/"); + mark_glob_match_recursive(this->root.get(), pth, glob); +} + std::shared_ptr Tree::deserialize( const slamd::flatb::Tree* serialized, std::map>& From 77203b6788efa194c9ae3c0c176ede50349380eb Mon Sep 17 00:00:00 2001 From: Robertleoj Date: Sun, 14 Sep 2025 19:53:18 +0200 Subject: [PATCH 6/6] bump version --- python_bindings/pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python_bindings/pyproject.toml b/python_bindings/pyproject.toml index 8c42c159..0c39603a 100644 --- a/python_bindings/pyproject.toml +++ b/python_bindings/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "scikit_build_core.build" [project] name = "slamd" -version = "2.1.9" +version = "2.1.10" description = "Python bindings for SlamDunk" authors = [{ name = "Robert Leo", email = "robert.leo.jonsson@gmail.com" }] readme = "README.md"