Ember
Loading...
Searching...
No Matches
ForgeTreeCanvas.cpp
Go to the documentation of this file.
3#include "Utils/Logger.h"
6#include <cctype>
7
8namespace EmberForge {
9
10ForgeTreeCanvas::ForgeTreeCanvas(wxWindow *parent, wxWindowID id) : EmberUI::TreeCanvas(parent, id) {
12
13 auto &config = GetConfig();
14 config.icon_directory = ResourcePath::GetDir("icons/tree_nodes").ToStdString();
16}
17
20 const auto &btView = prefs.GetBehaviorTreeViewSettings();
21 const auto &mainPanel = prefs.GetMainPanelSettings();
22 const auto &perf = prefs.GetPerformanceSettings();
23
24 auto &cfg = GetConfig();
25
26 cfg.zoom_step = btView.zoomStepSize;
27 cfg.mouse_wheel_sensitivity = btView.mouseWheelSensitivity;
28 cfg.pan_sensitivity = btView.panSensitivity;
29 cfg.zoom_follows_cursor = btView.zoomFollowsCursor;
30 cfg.enable_smooth_panning = btView.enableSmoothPanning;
31 cfg.pan_smoothness = btView.panSmoothness;
32 cfg.pan_step_size = btView.panStepSize;
33
34 cfg.enable_viewport_culling = perf.enableViewportCulling;
35 cfg.viewport_culling_margin = perf.viewportCullingMargin;
36
37 SetShowGrid(mainPanel.showGrid);
38 SetShowMinimap(mainPanel.showMinimap);
39 SetShowBreadcrumb(mainPanel.showCanvasBreadcrumb);
40 cfg.grid_size = mainPanel.gridSize;
41 cfg.grid_color = wxColour(mainPanel.gridLineColor.r, mainPanel.gridLineColor.g, mainPanel.gridLineColor.b);
42
43 cfg.highlight_path_to_selected = mainPanel.highlightPathToSelected;
44 cfg.path_highlight_color =
45 wxColour(mainPanel.pathHighlightColor.r, mainPanel.pathHighlightColor.g, mainPanel.pathHighlightColor.b);
46
47 cfg.background_color = wxColour(mainPanel.canvasBackgroundColor.r, mainPanel.canvasBackgroundColor.g,
48 mainPanel.canvasBackgroundColor.b);
49
50 cfg.connection_color =
51 wxColour(mainPanel.connectionLineColor.r, mainPanel.connectionLineColor.g, mainPanel.connectionLineColor.b);
52
53 cfg.node_color = wxColour(mainPanel.idleNodeBgColor.r, mainPanel.idleNodeBgColor.g, mainPanel.idleNodeBgColor.b);
54 cfg.border_color =
55 wxColour(mainPanel.idleNodeBorderColor.r, mainPanel.idleNodeBorderColor.g, mainPanel.idleNodeBorderColor.b);
56 cfg.text_color =
57 wxColour(mainPanel.idleNodeTextColor.r, mainPanel.idleNodeTextColor.g, mainPanel.idleNodeTextColor.b);
58 cfg.selected_border_color =
59 wxColour(mainPanel.selectedNodeColor.r, mainPanel.selectedNodeColor.g, mainPanel.selectedNodeColor.b);
60 cfg.hovered_border_color =
61 wxColour(mainPanel.hoveredNodeColor.r, mainPanel.hoveredNodeColor.g, mainPanel.hoveredNodeColor.b);
62
63 SetShowOverlayInfo(false);
64
65 LOG_INFO("ForgeTreeCanvas", "Preferences loaded");
66}
67
70 const auto &btView = prefs.GetBehaviorTreeViewSettings();
71
72 dc.SetUserScale(1.0, 1.0);
73 dc.SetDeviceOrigin(0, 0);
74
75 wxSize panelSize = GetSize();
76
77 auto calcPos = [&panelSize](AppPreferences::TextAnchor anchor, int offX, int offY) -> wxPoint {
78 switch (anchor) {
80 return {panelSize.x - offX, offY};
82 return {offX, panelSize.y - offY};
84 return {panelSize.x - offX, panelSize.y - offY};
85 default:
86 return {offX, offY};
87 }
88 };
89
90 if (btView.showCoordinateInfo) {
91 wxColour c(btView.coordinateInfoColor.r, btView.coordinateInfoColor.g, btView.coordinateInfoColor.b);
92 dc.SetTextForeground(c);
93 dc.SetFont(
94 wxFont(btView.coordinateInfoFontSize, wxFONTFAMILY_TELETYPE, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_NORMAL));
95 wxPoint viewOfs = GetViewOffset();
96 wxString zoomText = wxString::Format("Zoom: %.0f%% | Pan: (%d, %d)", GetZoom() * 100, viewOfs.x, viewOfs.y);
97 if (IsInFocusMode())
98 zoomText += " | FOCUS MODE";
99 dc.DrawText(zoomText, calcPos(btView.coordinateInfoAnchor, btView.coordinateInfoX, btView.coordinateInfoY));
100 }
101
102 auto *selectedNode = GetSelectedNode();
103 if (btView.showSelectedNodeInfo && selectedNode) {
104 wxColour c(btView.selectedNodeInfoColor.r, btView.selectedNodeInfoColor.g, btView.selectedNodeInfoColor.b);
105 dc.SetTextForeground(c);
106 dc.SetFont(
107 wxFont(btView.selectedNodeInfoFontSize, wxFONTFAMILY_TELETYPE, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_NORMAL));
108 EmberCore::String selectedText =
109 "Selected: " + selectedNode->GetName() + " [" + selectedNode->GetTypeString() + "]";
110
111 auto *statusProvider = GetStatusProvider();
112 if (statusProvider) {
113 int status = statusProvider->GetNodeStatus(static_cast<int64_t>(selectedNode->GetId()));
114 wxString statusNames[] = {"Idle", "Running", "Success", "Failure", "Halted"};
115 selectedText += " | Status: " + (status >= 0 && status <= 4 ? statusNames[status] : "Unknown");
116 }
117 dc.DrawText(selectedText,
118 calcPos(btView.selectedNodeInfoAnchor, btView.selectedNodeInfoX, btView.selectedNodeInfoY));
119 }
120
121 if (btView.showTreeInfo) {
122 auto tree = GetTree();
123 if (tree && tree->HasRootNode()) {
124 wxColour c(btView.treeInfoColor.r, btView.treeInfoColor.g, btView.treeInfoColor.b);
125 dc.SetTextForeground(c);
126 dc.SetFont(wxFont(btView.treeInfoFontSize, wxFONTFAMILY_TELETYPE, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_NORMAL));
127 wxString statsText = wxString::Format("Nodes: %lu", static_cast<unsigned long>(tree->GetNodeCount()));
128 dc.DrawText(statsText, calcPos(btView.treeInfoAnchor, btView.treeInfoX, btView.treeInfoY));
129 }
130 }
131
132 if (btView.showFPS) {
133 wxColour c(btView.fpsColor.r, btView.fpsColor.g, btView.fpsColor.b);
134 dc.SetTextForeground(c);
135 dc.SetFont(wxFont(btView.fpsFontSize, wxFONTFAMILY_TELETYPE, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_BOLD));
137 wxString txt = wxString::Format("FPS: %.1f", mon.GetCurrentFPS());
138 dc.DrawText(txt, calcPos(btView.fpsAnchor, btView.fpsX, btView.fpsY));
139 }
140
141 if (btView.showControlsHelp) {
142 wxColour c(btView.controlsHelpColor.r, btView.controlsHelpColor.g, btView.controlsHelpColor.b);
143 dc.SetTextForeground(c);
144 dc.SetFont(wxFont(btView.controlsHelpFontSize, wxFONTFAMILY_DEFAULT, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_NORMAL));
145 EmberCore::String helpText = "Controls: Click=Select | DblClick=Center | Wheel=Zoom | " +
146 btView.resetViewHotkey + "=Reset | " + btView.centerOnNodeHotkey +
147 "=Expand/Collapse | F=Focus | " + btView.deleteNodeHotkey + "=Delete";
148 if (IsInFocusMode())
149 helpText += " | Esc=Exit Focus";
150 dc.DrawText(helpText, calcPos(btView.controlsHelpAnchor, btView.controlsHelpX, btView.controlsHelpY));
151 }
152
154}
155
156bool ForgeTreeCanvas::MatchesHotkey(int keyCode, const EmberCore::String &hotkeyStr) const {
157 if (hotkeyStr.empty())
158 return false;
159
160 if (hotkeyStr == "Up")
161 return keyCode == WXK_UP;
162 if (hotkeyStr == "Down")
163 return keyCode == WXK_DOWN;
164 if (hotkeyStr == "Left")
165 return keyCode == WXK_LEFT;
166 if (hotkeyStr == "Right")
167 return keyCode == WXK_RIGHT;
168 if (hotkeyStr == "Delete")
169 return keyCode == WXK_DELETE;
170 if (hotkeyStr == "Escape")
171 return keyCode == WXK_ESCAPE;
172 if (hotkeyStr == "Space")
173 return keyCode == WXK_SPACE;
174 if (hotkeyStr == "Tab")
175 return keyCode == WXK_TAB;
176 if (hotkeyStr == "Enter" || hotkeyStr == "Return")
177 return keyCode == WXK_RETURN;
178
179 if (hotkeyStr.length() == 1) {
180 char ch = std::toupper(static_cast<unsigned char>(hotkeyStr[0]));
181 return keyCode == static_cast<int>(ch);
182 }
183
184 return false;
185}
186
187void ForgeTreeCanvas::OnKeyDown(wxKeyEvent &event) {
189 const auto &btView = prefs.GetBehaviorTreeViewSettings();
190 int keyCode = event.GetKeyCode();
191
192 float panStep = GetConfig().pan_step_size * GetConfig().pan_sensitivity;
193
194 if (MatchesHotkey(keyCode, btView.resetViewHotkey)) {
195 ResetView();
196 MarkDirty();
197 return;
198 }
199 if (MatchesHotkey(keyCode, btView.centerOnNodeHotkey)) {
200 auto *sel = GetSelectedNode();
201 if (sel && sel->GetChildCount() > 0) {
202 bool visible = sel->AreChildrenVisible();
203 sel->SetChildrenVisible(!visible);
205 }
206 return;
207 }
208 if (MatchesHotkey(keyCode, btView.panUpHotkey)) {
209 SetTargetOffset(GetTargetOffset() + wxPoint(0, static_cast<int>(panStep)));
210 MarkDirty();
211 return;
212 }
213 if (MatchesHotkey(keyCode, btView.panDownHotkey)) {
214 SetTargetOffset(GetTargetOffset() + wxPoint(0, -static_cast<int>(panStep)));
215 MarkDirty();
216 return;
217 }
218 if (MatchesHotkey(keyCode, btView.panLeftHotkey)) {
219 SetTargetOffset(GetTargetOffset() + wxPoint(static_cast<int>(panStep), 0));
220 MarkDirty();
221 return;
222 }
223 if (MatchesHotkey(keyCode, btView.panRightHotkey)) {
224 SetTargetOffset(GetTargetOffset() + wxPoint(-static_cast<int>(panStep), 0));
225 MarkDirty();
226 return;
227 }
228
229 TreeCanvas::OnKeyDown(event);
230}
231
232wxColour ForgeTreeCanvas::GetNodeFillColor(EmberCore::ITreeNode *node, bool selected, bool hovered) {
234 const auto &mp = prefs.GetMainPanelSettings();
235
236 wxColour base(mp.idleNodeBgColor.r, mp.idleNodeBgColor.g, mp.idleNodeBgColor.b);
237
238 if (selected)
239 return ApplyTint(base, mp.selectedNodeBgTint);
240 if (hovered)
241 return ApplyTint(base, mp.hoveredNodeBgTint);
242
243 if (node->IsSubTreeReference()) {
244 return wxColour(std::min(255, base.Red() + 10), std::min(255, base.Green() + 10),
245 std::max(0, base.Blue() - 20));
246 }
247 return base;
248}
249
250wxColour ForgeTreeCanvas::GetNodeBorderColor(EmberCore::ITreeNode *node, bool selected, bool hovered) {
252 const auto &mp = prefs.GetMainPanelSettings();
253
254 if (selected)
255 return wxColour(mp.selectedNodeColor.r, mp.selectedNodeColor.g, mp.selectedNodeColor.b);
256 if (hovered)
257 return wxColour(mp.hoveredNodeColor.r, mp.hoveredNodeColor.g, mp.hoveredNodeColor.b);
258
259 if (node->IsSubTreeReference())
260 return wxColour(80, 120, 200);
261
262 return wxColour(mp.idleNodeBorderColor.r, mp.idleNodeBorderColor.g, mp.idleNodeBorderColor.b);
263}
264
265wxColour ForgeTreeCanvas::GetNodeTextColor(EmberCore::ITreeNode *node, bool selected, bool hovered) {
267 const auto &mp = prefs.GetMainPanelSettings();
268
269 if (selected)
270 return wxColour(mp.selectedNodeTextColor.r, mp.selectedNodeTextColor.g, mp.selectedNodeTextColor.b);
271 if (hovered)
272 return wxColour(mp.hoveredNodeTextColor.r, mp.hoveredNodeTextColor.g, mp.hoveredNodeTextColor.b);
273
274 return wxColour(mp.idleNodeTextColor.r, mp.idleNodeTextColor.g, mp.idleNodeTextColor.b);
275}
276
277wxColour ForgeTreeCanvas::ApplyTint(const wxColour &color, float tint) {
278 int r = color.Red();
279 int g = color.Green();
280 int b = color.Blue();
281
282 if (tint > 0) {
283 r = r + static_cast<int>((255 - r) * tint);
284 g = g + static_cast<int>((255 - g) * tint);
285 b = b + static_cast<int>((255 - b) * tint);
286 } else {
287 r = r + static_cast<int>(r * tint);
288 g = g + static_cast<int>(g * tint);
289 b = b + static_cast<int>(b * tint);
290 }
291
292 return wxColour(std::max(0, std::min(255, r)), std::max(0, std::min(255, g)), std::max(0, std::min(255, b)));
293}
294
295} // namespace EmberForge
#define LOG_INFO(category, message)
Definition Logger.h:114
#define PERF_END_FRAME()
Centralized resource path management for EmberForge.
Abstract interface for tree nodes that can be visualized.
Definition ITreeNode.h:31
virtual bool IsSubTreeReference() const
Check if this node is a SubTree reference.
Definition ITreeNode.h:100
static AppPreferencesManager & GetInstance()
BehaviorTreeViewSettings & GetBehaviorTreeViewSettings()
MainPanelSettings & GetMainPanelSettings()
TextAnchor
Text anchor position for debug info overlay.
static wxColour ApplyTint(const wxColour &color, float tint)
bool MatchesHotkey(int keyCode, const EmberCore::String &hotkeyStr) const
wxColour GetNodeTextColor(EmberCore::ITreeNode *node, bool selected, bool hovered) override
Returns the text color for a node; override for custom coloring.
wxColour GetNodeFillColor(EmberCore::ITreeNode *node, bool selected, bool hovered) override
Returns the fill color for a node; override for custom coloring.
void OnKeyDown(wxKeyEvent &event) override
Key down handler; override for custom key handling.
wxColour GetNodeBorderColor(EmberCore::ITreeNode *node, bool selected, bool hovered) override
Returns the border color for a node; override for custom coloring.
void OnBeforePaintOverlays(wxDC &dc) override
Called before painting overlays; override to draw custom overlays.
ForgeTreeCanvas(wxWindow *parent, wxWindowID id=wxID_ANY)
System performance monitoring class.
static PerformanceMonitor & GetInstance()
static wxString GetDir(const wxString &relativeDir)
Get the full path to a resource directory.
float GetZoom() const
Returns the current zoom factor.
Definition TreeCanvas.h:120
TreeCanvas(wxWindow *parent, wxWindowID id=wxID_ANY)
Constructor.
void SetShowMinimap(bool show)
Enables or disables minimap display.
Definition TreeCanvas.h:140
void InvalidateLayout()
Invalidates layout cache and triggers refresh.
Definition TreeCanvas.h:128
IStatusProvider * GetStatusProvider() const
Returns the status overlay provider.
Definition TreeCanvas.h:99
void SetShowOverlayInfo(bool show)
Enables or disables overlay info display.
Definition TreeCanvas.h:138
void SetTargetOffset(const wxPoint &offset)
Sets the target offset for smooth panning.
Definition TreeCanvas.h:177
void ResetView()
Resets zoom and pan to default values.
void MarkDirty()
Marks the canvas for repaint.
Definition TreeCanvas.h:123
wxPoint GetTargetOffset() const
Returns the target offset for smooth panning.
Definition TreeCanvas.h:175
void LoadTypeIcons()
Loads type icons from the configured icon directory.
void SetShowBreadcrumb(bool show)
Enables or disables breadcrumb display.
Definition TreeCanvas.h:142
EmberCore::ITreeNode * GetSelectedNode() const
Returns the currently selected node.
Definition TreeCanvas.h:102
void SetShowGrid(bool show)
Enables or disables grid display.
Definition TreeCanvas.h:136
TreeCanvasConfig & GetConfig()
Returns mutable configuration.
Definition TreeCanvas.h:92
bool IsInFocusMode() const
Returns whether focus mode is active.
Definition TreeCanvas.h:149
std::shared_ptr< EmberCore::ITreeStructure > GetTree() const
Returns the current tree structure.
Definition TreeCanvas.h:89
wxPoint GetViewOffset() const
Returns the current view offset.
Definition TreeCanvas.h:173
std::string String
Framework-agnostic string type.
Definition String.h:14
Definition Panel.h:8