-
Notifications
You must be signed in to change notification settings - Fork 8
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Replace linear node list with tree representation (#34)
* fixed clang tidy warnings * refactored code to reduce complexity of main file * replaced linear list of nodes with foldable structure Co-authored-by: Dominik <45536968+authaldo@users.noreply.github.com> Co-authored-by: Jonas Otto <jonas@jonasotto.com>
- Loading branch information
Showing
11 changed files
with
755 additions
and
419 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
/** | ||
* @file node_window.hpp | ||
* @author Dominik Authaler | ||
* @date 13.01.2024 | ||
* | ||
* Code related to the node window within the graphical user interface. | ||
*/ | ||
|
||
#ifndef RIG_RECONFIGURE_NODE_WINDOW_HPP | ||
#define RIG_RECONFIGURE_NODE_WINDOW_HPP | ||
|
||
#include "utils.hpp" | ||
#include "service_wrapper.hpp" | ||
|
||
/** | ||
* Renders the window for the node selection. | ||
* @param[in] windowName Window name. | ||
* @param[in] nodeNames List with the available nodes. | ||
* @param[in, out] serviceWrapper Service wrapper for issuing ROS requests. | ||
* @param[in, out] selectedNode Full name of the currently selected node. | ||
* @param[in, out] status Status. | ||
*/ | ||
void renderNodeWindow(const char *windowName, const std::vector<std::string> &nodeNames, | ||
ServiceWrapper &serviceWrapper, std::string &selectedNode, Status &status); | ||
|
||
#endif //RIG_RECONFIGURE_NODE_WINDOW_HPP |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
/** | ||
* @file parameter_window.hpp | ||
* @author Dominik Authaler | ||
* @date 13.01.2024 | ||
* | ||
* Code related to the parameter window within the graphical user interface. | ||
*/ | ||
|
||
#ifndef RIG_RECONFIGURE_PARAMETER_WINDOW_HPP | ||
#define RIG_RECONFIGURE_PARAMETER_WINDOW_HPP | ||
|
||
#include <imgui.h> | ||
#include <string> | ||
|
||
#include "parameter_tree.hpp" | ||
#include "service_wrapper.hpp" | ||
#include "utils.hpp" | ||
|
||
/** | ||
* Renders the window for the parameter modification. | ||
* @param[in] windowName Window name. | ||
* @param[in] curSelectedNode Name of the currently selected node. | ||
* @param[in, out] serviceWrapper Service wrapper for issuing ROS requests. | ||
* @param[in, out] filteredParameterTree Filtered parameter tree. | ||
* @param[in, out] filter Filter string input. | ||
* @param[in, out] status Status for displaying errors. | ||
*/ | ||
void renderParameterWindow(const char *windowName, const std::string &curSelectedNode, | ||
ServiceWrapper &serviceWrapper, ParameterTree &filteredParameterTree, | ||
std::string &filter, Status &status); | ||
|
||
#endif //RIG_RECONFIGURE_PARAMETER_WINDOW_HPP |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,68 @@ | ||
/** | ||
* @file utils.hpp | ||
* @author Dominik Authaler | ||
* @date 12.01.2024 | ||
* | ||
* Collection of utility functions. | ||
*/ | ||
|
||
#ifndef RIG_RECONFIGURE_UTILS_HPP | ||
#define RIG_RECONFIGURE_UTILS_HPP | ||
|
||
#include <string> | ||
#include <imgui.h> | ||
#include <filesystem> | ||
|
||
#include <GLFW/glfw3.h> // will drag system OpenGL headers | ||
|
||
struct Status { | ||
enum class Type { NONE, NO_NODES_AVAILABLE, PARAMETER_CHANGED, SERVICE_TIMEOUT }; | ||
|
||
Type type = Type::NONE; | ||
std::string text; | ||
}; | ||
|
||
/** | ||
* Utility imgui function for partly highlighted text. | ||
* @param text String which should be displayed. | ||
* @param start Starting index of the highlighted part. | ||
* @param end End index of the highlighted part. | ||
* @param highlightColor Color used for the highlighted part, the remaining text is displayed using the default text | ||
* color. | ||
*/ | ||
void highlightedText(const std::string &text, std::size_t start, std::size_t end, | ||
const ImVec4 &highlightColor); | ||
|
||
/** | ||
* Utility imgui function for partly highlighted text which can be selected. | ||
* @param text String which should be displayed. | ||
* @param start Starting index of the highlighted part. | ||
* @param end End index of the highlighted part. | ||
* @param highlightColor Color used for the highlighted part, the remaining text is displayed using the default text | ||
* color. | ||
*/ | ||
bool highlightedSelectableText(const std::string &text, std::size_t start, std::size_t end, | ||
const ImVec4 &highlightColor); | ||
|
||
/** | ||
* Searches for the resource directory. | ||
* @param execPath Executable path. | ||
* @return Path to the resource directory. | ||
*/ | ||
std::filesystem::path findResourcePath(const std::string &execPath); | ||
|
||
/** | ||
* Loads an icon for the provided window. | ||
* @param windowPtr Window for which the icon should be loaded. | ||
* @param resourcePath Path to the icon data. | ||
*/ | ||
void loadWindowIcon(GLFWwindow *windowPtr, const std::filesystem::path &resourcePath); | ||
|
||
/** | ||
* Prints the corresponding error. | ||
* @param error Error code. | ||
* @param description Detailed error description. | ||
*/ | ||
void glfw_error_callback(int error, const char *description); | ||
|
||
#endif //RIG_RECONFIGURE_UTILS_HPP |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,196 @@ | ||
/** | ||
* @file node_window.cpp | ||
* @author Dominik Authaler | ||
* @date 13.01.2024 | ||
* | ||
* Code related to the node window within the graphical user interface. | ||
*/ | ||
|
||
#include "node_window.hpp" | ||
|
||
#include <imgui.h> | ||
#include <string> | ||
|
||
// height of the box in which the nodes are visualized | ||
static constexpr auto BOX_HEIGHT = 500; | ||
|
||
// utility structures and functions (definitions mainly follow at the end of the file) | ||
struct TreeNode { | ||
std::string name; // node name (leaf node) / namespace (other) | ||
std::string fullName; // full node name for easier usage | ||
|
||
std::vector<std::shared_ptr<TreeNode>> children; | ||
}; | ||
|
||
class NodeTree { | ||
public: | ||
explicit NodeTree(const std::vector<std::string> &nodes); | ||
|
||
std::shared_ptr<TreeNode> getRoot(); | ||
|
||
private: | ||
struct SortComparator { | ||
inline bool operator() (const std::shared_ptr<TreeNode>& node1, const std::shared_ptr<TreeNode>& node2); | ||
}; | ||
|
||
/** | ||
* Adds a new node within the node tree. | ||
* @param curNode Root node of the (sub-)tree. | ||
* @param name (Partial) node name that is considered for inserting the node within the tree. | ||
* @param fullName Full name of the new node (stored for convenient access to selected nodes). | ||
*/ | ||
void addNode(const std::shared_ptr<TreeNode>& curNode, const std::string &name, const std::string &fullName); | ||
|
||
/** | ||
* Reorders children of nodes: | ||
* - leaf nodes before inner nodes | ||
* - alphabetically within same groups | ||
*/ | ||
void sortAlphabetically(const std::shared_ptr<TreeNode>& curNode); | ||
|
||
std::shared_ptr<TreeNode> root; | ||
}; | ||
|
||
void visualizeNodeTree(const std::shared_ptr<const TreeNode>& root, std::string &selectedNode); | ||
|
||
void renderNodeWindow(const char *windowName, const std::vector<std::string> &nodeNames, | ||
ServiceWrapper &serviceWrapper, std::string &selectedNode, Status &status) { | ||
ImGui::Begin(windowName); | ||
|
||
if (nodeNames.empty()) { | ||
ImGui::Text("No nodes available!"); | ||
} else { | ||
ImGui::Text("Available nodes:"); | ||
|
||
// organize nodes as a (sorted) tree and visualize them in a foldable structure | ||
NodeTree tree(nodeNames); | ||
|
||
// the list box creates a highlighted area in which scrolling is possible | ||
if (ImGui::BeginListBox("##Nodes", ImVec2(-1, BOX_HEIGHT))) { | ||
visualizeNodeTree(tree.getRoot(), selectedNode); | ||
ImGui::EndListBox(); | ||
} | ||
} | ||
|
||
if (ImGui::Button("Refresh")) { | ||
serviceWrapper.pushRequest(std::make_shared<Request>(Request::Type::QUERY_NODE_NAMES)); | ||
|
||
if (status.type == Status::Type::SERVICE_TIMEOUT) { | ||
status.text.clear(); | ||
status.type = Status::Type::NONE; | ||
} | ||
} | ||
|
||
ImGui::End(); | ||
} | ||
|
||
// implementation of utility + member functions | ||
NodeTree::NodeTree(const std::vector<std::string> &nodes) : root(std::make_shared<TreeNode>()) { | ||
for (const auto &node : nodes) { | ||
// we ignore the leading slash for building the tree | ||
addNode(root, node.substr(1), node); | ||
} | ||
|
||
sortAlphabetically(root); | ||
} | ||
|
||
std::shared_ptr<TreeNode> NodeTree::getRoot() { | ||
return root; | ||
} | ||
|
||
void NodeTree::sortAlphabetically(const std::shared_ptr<TreeNode>& curNode) { | ||
for (const auto &child : curNode->children) { | ||
sortAlphabetically(child); | ||
} | ||
|
||
std::sort(curNode->children.begin(), curNode->children.end(), SortComparator()); | ||
} | ||
|
||
void NodeTree::addNode(const std::shared_ptr<TreeNode> &curNode, const std::string &name, const std::string &fullName) { | ||
|
||
auto prefixEnd = name.find('/'); | ||
if (prefixEnd == std::string::npos) { | ||
curNode->children.emplace_back(std::make_shared<TreeNode>(TreeNode{name, fullName})); | ||
return; | ||
} | ||
|
||
// extract first prefix and find corresponding node | ||
auto prefix = name.substr(0, prefixEnd); | ||
auto remainingName = name.substr(prefixEnd + 1); | ||
|
||
std::shared_ptr<TreeNode> nextNode = nullptr; | ||
for (const auto &child : curNode->children) { | ||
if (child->name.starts_with(prefix)) { | ||
nextNode = child; | ||
break; | ||
} | ||
} | ||
|
||
if (nextNode == nullptr) { | ||
nextNode = std::make_shared<TreeNode>(TreeNode{name, fullName}); | ||
curNode->children.emplace_back(nextNode); | ||
} else { | ||
// found an existing node, check whether we have to subdivide it | ||
const auto idx = name.find('/'); | ||
if (nextNode->children.empty() && idx != std::string::npos && nextNode->name.find('/') != std::string::npos) { | ||
// nextNode is leaf with prefix (namespace with single child is collapsed) | ||
auto nextNodePrefix = nextNode->name.substr(0, idx); | ||
auto nextNodeRemainingName = nextNode->name.substr(idx + 1); | ||
|
||
nextNode->children.emplace_back(std::make_shared<TreeNode>(TreeNode{nextNodeRemainingName, nextNode->fullName})); | ||
|
||
nextNode->name = nextNodePrefix; | ||
nextNode->fullName.clear(); | ||
} else if (nextNode->children.empty() && idx != std::string::npos && nextNode->name == prefix) { | ||
// nextNode is a leaf node with same name as next namespace token | ||
// create sibling to nextNode with same name, and add node below that | ||
nextNode = std::make_shared<TreeNode>(TreeNode{prefix, fullName}); | ||
curNode->children.emplace_back(nextNode); | ||
} | ||
|
||
addNode(nextNode, remainingName, fullName); | ||
} | ||
} | ||
|
||
bool NodeTree::SortComparator::operator()(const std::shared_ptr<TreeNode> &node1, | ||
const std::shared_ptr<TreeNode> &node2) { | ||
bool res; | ||
|
||
if (node1->children.empty() && !node2->children.empty()) { | ||
res = true; | ||
} else if (!node1->children.empty() && node2->children.empty()) { | ||
res = false; | ||
} else { | ||
res = (node1->name < node2->name); | ||
} | ||
|
||
return res; | ||
} | ||
|
||
void visualizeNodeTree(const std::shared_ptr<const TreeNode>& root, std::string &selectedNode) { | ||
if (root->children.empty()) { | ||
// leaf node | ||
// push "leaf" to id stack to prevent ID collision between node and namespace with same name | ||
ImGui::PushID("leaf"); | ||
if (ImGui::Selectable(root->name.c_str(), selectedNode == root->fullName)) { | ||
|
||
selectedNode = root->fullName; | ||
} | ||
ImGui::PopID(); | ||
} else { | ||
// inner node | ||
if (!root->name.empty()) { | ||
if (ImGui::TreeNode(root->name.c_str())) { | ||
for (const auto &child : root->children) { | ||
visualizeNodeTree(child, selectedNode); | ||
} | ||
|
||
ImGui::TreePop(); | ||
} | ||
} else { | ||
for (const auto &child : root->children) { | ||
visualizeNodeTree(child, selectedNode); | ||
} | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.