Ember
Loading...
Searching...
No Matches
TreeWidget.cpp
Go to the documentation of this file.
3#include "Utils/Logger.h"
4#include <algorithm>
5#include <cmath>
6#include <wx/dcbuffer.h>
7#include <wx/menu.h>
8
9namespace EmberForge {
10
11// Define static constexpr members for C++14 compatibility
12constexpr double TreeWidget::MIN_ZOOM;
13constexpr double TreeWidget::MAX_ZOOM;
14
15// Event table
17 EVT_ERASE_BACKGROUND(TreeWidget::OnEraseBackground) EVT_LEFT_DOWN(TreeWidget::OnMouseDown)
18 EVT_LEFT_UP(TreeWidget::OnMouseUp) EVT_MOTION(TreeWidget::OnMouseMove) EVT_MOUSEWHEEL(TreeWidget::OnMouseWheel)
19 EVT_CONTEXT_MENU(TreeWidget::OnRightClick) EVT_KEY_DOWN(TreeWidget::OnKeyDown) EVT_CHAR(TreeWidget::OnChar)
21
22 TreeWidget::TreeWidget(wxWindow *parent, std::shared_ptr<EmberCore::BehaviorTree> tree)
23 : wxScrolledWindow(parent, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxHSCROLL | wxVSCROLL | wxTAB_TRAVERSAL),
24 behavior_tree_(tree), layout_algorithm_(LayoutAlgorithm::Vertical), interaction_mode_(InteractionMode::View),
25 zoom_level_(1.0), view_offset_(0, 0), tree_size_(800, 600), node_render_style_(NodeWidget::RenderStyle::Normal),
26 zoom_step_(0.1), mouse_wheel_sensitivity_(1.0), pan_sensitivity_(1.0), zoom_follows_cursor_(true),
27 background_color_(wxColour(250, 250, 250)), grid_color_(wxColour(200, 200, 200)),
28 connection_color_(wxColour(100, 100, 100)), connection_width_(2), grid_visible_(true), connections_visible_(true),
29 is_dragging_(false), is_selecting_(false), node_spacing_x_(DEFAULT_NODE_SPACING_X),
30 node_spacing_y_(DEFAULT_NODE_SPACING_Y), level_spacing_(DEFAULT_LEVEL_SPACING), tree_root_position_(50, 50),
31 animation_timer_(nullptr) {
32 InitializeWidget();
33
34 if (behavior_tree_) {
35 CreateNodeWidgets();
36 RefreshLayout();
37 }
38}
39
47
48void TreeWidget::SetBehaviorTree(std::shared_ptr<EmberCore::BehaviorTree> tree) {
49 if (behavior_tree_ != tree) {
51 behavior_tree_ = tree;
52
53 if (behavior_tree_) {
56 }
57
58 Refresh();
59 }
60}
61
63 if (layout_algorithm_ != algorithm) {
64 layout_algorithm_ = algorithm;
66 }
67}
68
70 if (!behavior_tree_) {
71 return;
72 }
73
74 switch (layout_algorithm_) {
77 break;
80 break;
83 break;
86 break;
88 // Don't change positions in manual mode
89 break;
90 }
91
94 Refresh();
95}
96
98 if (!behavior_tree_) {
99 return;
100 }
101
102 // Calculate bounding box of all nodes
103 wxRect boundingBox;
104 bool first = true;
105
106 for (const auto &pair : node_widgets_) {
107 wxPoint pos = CalculateNodePosition(pair.first);
108 wxSize size = pair.second->GetSize();
109 wxRect nodeRect(pos, size);
110
111 if (first) {
112 boundingBox = nodeRect;
113 first = false;
114 } else {
115 boundingBox = boundingBox.Union(nodeRect);
116 }
117 }
118
119 if (!boundingBox.IsEmpty()) {
120 // Add padding
121 boundingBox.Inflate(50);
122 tree_size_ = boundingBox.GetSize();
123 tree_root_position_ = boundingBox.GetTopLeft();
124 }
125
127}
128
130 interaction_mode_ = mode;
131
132 // Update cursor based on mode
133 switch (mode) {
135 SetCursor(wxCursor(wxCURSOR_CROSS));
136 break;
138 SetCursor(wxCursor(wxCURSOR_HAND));
139 break;
140 default:
141 SetCursor(wxCursor(wxCURSOR_ARROW));
142 break;
143 }
144}
145
147 auto it = node_widgets_.find(node);
148 if (it != node_widgets_.end()) {
149 return it->second.get();
150 }
151 return nullptr;
152}
153
154std::vector<NodeWidget *> TreeWidget::GetAllNodeWidgets() const {
155 std::vector<NodeWidget *> widgets;
156 for (const auto &pair : node_widgets_) {
157 widgets.push_back(pair.second.get());
158 }
159 return widgets;
160}
161
162std::vector<EmberForge::NodeWidget *> TreeWidget::GetSelectedNodeWidgets() const {
163 std::vector<EmberForge::NodeWidget *> widgets;
164 for (EmberCore::Node *node : selected_nodes_) {
166 if (widget) {
167 widgets.push_back(widget);
168 }
169 }
170 return widgets;
171}
172
173void TreeWidget::SelectNode(EmberCore::Node *node, bool add_to_selection) {
174 if (!node)
175 return;
176
177 if (!add_to_selection) {
179 }
180
181 auto it = std::find(selected_nodes_.begin(), selected_nodes_.end(), node);
182 if (it == selected_nodes_.end()) {
183 selected_nodes_.push_back(node);
186 }
187}
188
190 auto it = std::find(selected_nodes_.begin(), selected_nodes_.end(), node);
191 if (it != selected_nodes_.end()) {
192 selected_nodes_.erase(it);
195 }
196}
197
199 if (!selected_nodes_.empty()) {
200 selected_nodes_.clear();
203 }
204}
205
207 if (!behavior_tree_)
208 return;
209
210 selected_nodes_.clear();
211 for (const auto &pair : node_widgets_) {
212 selected_nodes_.push_back(pair.first);
213 }
214
217}
218
220
221std::vector<EmberCore::Node *> TreeWidget::GetSelectedNodes() const { return selected_nodes_; }
222
224
226
228 if (!behavior_tree_ || node_widgets_.empty()) {
229 return;
230 }
231
232 // Calculate bounding box
233 wxSize clientSize = GetClientSize();
234 wxSize contentSize = CalculateTreeSize();
235
236 if (contentSize.x > 0 && contentSize.y > 0) {
237 double zoomX = static_cast<double>(clientSize.x) / contentSize.x;
238 double zoomY = static_cast<double>(clientSize.y) / contentSize.y;
239 double newZoom = std::min(zoomX, zoomY) * 0.9; // 90% to add margin
240
241 SetZoomLevel(newZoom);
242 CenterView();
243 }
244}
245
247 if (selected_nodes_.empty()) {
248 return;
249 }
250
251 // Calculate bounding box of selected nodes
252 wxRect boundingBox;
253 bool first = true;
254
255 for (EmberCore::Node *node : selected_nodes_) {
257 if (widget) {
258 wxRect nodeRect = widget->GetRect();
259 if (first) {
260 boundingBox = nodeRect;
261 first = false;
262 } else {
263 boundingBox = boundingBox.Union(nodeRect);
264 }
265 }
266 }
267
268 if (!boundingBox.IsEmpty()) {
269 wxSize clientSize = GetClientSize();
270 double zoomX = static_cast<double>(clientSize.x) / boundingBox.width;
271 double zoomY = static_cast<double>(clientSize.y) / boundingBox.height;
272 double newZoom = std::min(zoomX, zoomY) * 0.8;
273
274 SetZoomLevel(newZoom);
275
276 // Center on selection
277 wxPoint center(boundingBox.x + boundingBox.width / 2, boundingBox.y + boundingBox.height / 2);
278 view_offset_ = center - wxPoint(clientSize.x / 2, clientSize.y / 2);
280 }
281}
282
283void TreeWidget::SetZoomLevel(double zoom) {
284 zoom = std::max(MIN_ZOOM, std::min(MAX_ZOOM, zoom));
285
286 if (std::abs(zoom_level_ - zoom) > 0.001) {
287 zoom_level_ = zoom;
289 }
290}
291
293 NodeWidget *widget = GetNodeWidget(node);
294 if (widget) {
295 wxRect nodeRect = widget->GetRect();
296 wxPoint nodeCenter(nodeRect.x + nodeRect.width / 2, nodeRect.y + nodeRect.height / 2);
297 wxSize clientSize = GetClientSize();
298 view_offset_ = nodeCenter - wxPoint(clientSize.x / 2, clientSize.y / 2);
300 Refresh();
301 }
302}
303
305 if (!selected_nodes_.empty()) {
307 }
308}
309
311 wxSize clientSize = GetClientSize();
312 wxSize contentSize = CalculateTreeSize();
313
314 view_offset_ = wxPoint((contentSize.x - clientSize.x) / 2, (contentSize.y - clientSize.y) / 2);
316 Refresh();
317}
318
320 return parent != nullptr && interaction_mode_ == InteractionMode::Edit;
321}
322
323bool TreeWidget::AddNode(std::unique_ptr<EmberCore::Node> node, EmberCore::Node *parent) {
324 if (!CanAddNode(parent) || !node) {
325 return false;
326 }
327
328 // Add to behavior tree
329 EmberCore::Node *nodePtr = node.get();
330 if (behavior_tree_->AddNode(std::move(node), parent)) {
331 // Create widget for new node
332 auto widget = CreateNodeWidget(nodePtr);
333 if (widget) {
334 node_widgets_[nodePtr] = std::move(widget);
336
338 tree_change_callback_("node_added");
339 }
340
341 return true;
342 }
343 }
344
345 return false;
346}
347
350 return false;
351 }
352
353 // Remove nodes from behavior tree
354 for (EmberCore::Node *node : selected_nodes_) {
355 behavior_tree_->RemoveNode(node->GetId());
356
357 // Remove widget
358 auto it = node_widgets_.find(node);
359 if (it != node_widgets_.end()) {
360 node_widgets_.erase(it);
361 }
362 }
363
364 selected_nodes_.clear();
366
368 tree_change_callback_("nodes_removed");
369 }
370
371 return true;
372}
373
375 // Placeholder implementation
376 return false;
377}
378
380 return node && new_parent && interaction_mode_ == InteractionMode::Edit;
381}
382
384 if (!CanMoveNode(node, new_parent)) {
385 return false;
386 }
387
388 if (behavior_tree_->MoveNode(node->GetId(), new_parent)) {
390
392 tree_change_callback_("node_moved");
393 }
394
395 return true;
396 }
397
398 return false;
399}
400
402 if (node_render_style_ != style) {
403 node_render_style_ = style;
404
405 for (auto &pair : node_widgets_) {
406 pair.second->SetRenderStyle(style);
407 }
408
409 Refresh();
410 }
411}
412
413void TreeWidget::SetConnectionStyle(int line_width, const wxColour &color) {
414 connection_width_ = line_width;
415 connection_color_ = color;
416 Refresh();
417}
418
419void TreeWidget::SetBackgroundColor(const wxColour &color) {
420 background_color_ = color;
421 Refresh();
422}
423
424void TreeWidget::SetGridVisible(bool visible) {
425 grid_visible_ = visible;
426 Refresh();
427}
428
430 connections_visible_ = visible;
431 Refresh();
432}
433
435 // Placeholder for animation implementation
436 CenterOnNode(node);
437}
438
439void TreeWidget::HighlightExecutionPath(const std::vector<EmberCore::Node *> &path) {
440 highlighted_path_ = path;
441
442 // Update highlighting for all nodes
443 for (auto &pair : node_widgets_) {
444 bool highlighted = std::find(path.begin(), path.end(), pair.first) != path.end();
445 pair.second->SetHighlighted(highlighted);
446 }
447
448 Refresh();
449}
450
452 highlighted_path_.clear();
453
454 for (auto &pair : node_widgets_) {
455 pair.second->SetHighlighted(false);
456 }
457
458 Refresh();
459}
460
461bool TreeWidget::SaveLayout(const wxString &filename) const {
462 // Placeholder implementation
463 return false;
464}
465
466bool TreeWidget::LoadLayout(const wxString &filename) {
467 // Placeholder implementation
468 return false;
469}
470
471wxBitmap TreeWidget::ExportAsImage(const wxSize &size) const {
472 wxSize exportSize = size.IsFullySpecified() ? size : GetSize();
473 wxBitmap bitmap(exportSize);
474 wxMemoryDC dc(bitmap);
475
476 // Draw the tree to the bitmap
477 dc.SetBackground(wxBrush(background_color_));
478 dc.Clear();
479
480 // TODO: Implement drawing logic
481
482 return bitmap;
483}
484
485// Protected rendering methods
486void TreeWidget::OnPaint(wxPaintEvent &event) {
487 wxAutoBufferedPaintDC dc(this);
488 DoPrepareDC(dc);
489
490 // Scale for zoom
491 dc.SetUserScale(zoom_level_, zoom_level_);
492
493 DrawBackground(dc);
494
495 if (grid_visible_) {
496 DrawGrid(dc);
497 }
498
500 DrawConnections(dc);
501 }
502
503 if (is_selecting_) {
505 }
506}
507
508void TreeWidget::OnSize(wxSizeEvent &event) {
510 event.Skip();
511}
512
513void TreeWidget::OnEraseBackground(wxEraseEvent &event) {
514 // Prevent flicker
515}
516
517// Event handlers continue...
518void TreeWidget::OnMouseDown(wxMouseEvent &event) {
519 SetFocus();
520
521 wxPoint logicalPos = TreeToScreen(event.GetPosition());
522 NodeWidget *hitNode = HitTestNode(logicalPos);
523
524 if (hitNode) {
525 EmberCore::Node *node = hitNode->GetNode();
526 bool addToSelection = event.ControlDown();
527
528 if (!addToSelection ||
529 std::find(selected_nodes_.begin(), selected_nodes_.end(), node) == selected_nodes_.end()) {
530 SelectNode(node, addToSelection);
531 }
532
533 drag_start_pos_ = event.GetPosition();
534 is_dragging_ = true;
535 } else {
536 // Start selection rectangle
537 if (!event.ControlDown()) {
539 }
540
541 selection_start_pos_ = event.GetPosition();
542 is_selecting_ = true;
543 CaptureMouse();
544 }
545
546 event.Skip();
547}
548
549void TreeWidget::OnMouseUp(wxMouseEvent &event) {
550 if (HasCapture()) {
551 ReleaseMouse();
552 }
553
554 if (is_selecting_) {
555 // Finish selection rectangle
556 wxRect selectionRect(selection_start_pos_, event.GetPosition());
557 // Normalize rectangle
558
559 // Select nodes in rectangle
560 for (const auto &pair : node_widgets_) {
561 wxRect nodeRect = pair.second->GetRect();
562 if (selectionRect.Intersects(nodeRect)) {
563 SelectNode(pair.first, true);
564 }
565 }
566
567 is_selecting_ = false;
568 Refresh();
569 }
570
571 is_dragging_ = false;
572 event.Skip();
573}
574
575void TreeWidget::OnMouseMove(wxMouseEvent &event) {
576 if (is_selecting_) {
577 selection_rect_ = wxRect(selection_start_pos_, event.GetPosition());
578 Refresh();
579 }
580
581 event.Skip();
582}
583
584void TreeWidget::OnMouseWheel(wxMouseEvent &event) {
585 if (event.ControlDown()) {
586 // Zoom with preferences applied
587 double delta = (event.GetWheelRotation() > 0 ? zoom_step_ : -zoom_step_) * mouse_wheel_sensitivity_;
588
590 // Zoom towards cursor position
591 wxPoint mousePos = event.GetPosition();
592 wxPoint scrollPos = GetViewStart();
593 int pixelsPerUnitX, pixelsPerUnitY;
594 GetScrollPixelsPerUnit(&pixelsPerUnitX, &pixelsPerUnitY);
595
596 // Convert mouse position to world coordinates before zoom
597 double worldX = (mousePos.x + scrollPos.x * pixelsPerUnitX) / zoom_level_;
598 double worldY = (mousePos.y + scrollPos.y * pixelsPerUnitY) / zoom_level_;
599
600 double oldZoom = zoom_level_;
601 SetZoomLevel(zoom_level_ + delta);
602
603 // Adjust scroll to keep world point under cursor
604 if (std::abs(zoom_level_ - oldZoom) > 0.001) {
605 int newScrollX = static_cast<int>((worldX * zoom_level_ - mousePos.x) / pixelsPerUnitX);
606 int newScrollY = static_cast<int>((worldY * zoom_level_ - mousePos.y) / pixelsPerUnitY);
607 Scroll(newScrollX, newScrollY);
608 }
609 } else {
610 // Zoom towards center
611 SetZoomLevel(zoom_level_ + delta);
612 }
613 } else {
614 // Scroll
615 event.Skip();
616 }
617}
618
619void TreeWidget::OnRightClick(wxContextMenuEvent &event) { ShowContextMenu(event.GetPosition()); }
620
621void TreeWidget::OnKeyDown(wxKeyEvent &event) {
622 switch (event.GetKeyCode()) {
623 case WXK_DELETE:
625 break;
626 case WXK_CONTROL_A:
627 SelectAll();
628 break;
629 case WXK_ESCAPE:
631 break;
632 default:
633 event.Skip();
634 break;
635 }
636}
637
638void TreeWidget::OnChar(wxKeyEvent &event) { event.Skip(); }
639
640// Layout calculation methods (simplified implementations)
642 if (!behavior_tree_ || !behavior_tree_->GetRootNode()) {
643 return;
644 }
645
646 // Simple vertical tree layout
647 // This is a basic implementation - can be enhanced
648 EmberCore::Node *root = behavior_tree_->GetRootNode();
650}
651
653 // Placeholder implementation
654 CalculateVerticalLayout(); // Use vertical for now
655}
656
658 // Placeholder implementation
659 CalculateVerticalLayout(); // Use vertical for now
660}
661
663 // Placeholder implementation
664 CalculateVerticalLayout(); // Use vertical for now
665}
666
667// Helper method for vertical layout
668void TreeWidget::LayoutNodeVertical(EmberCore::Node *node, int x, int y, int level) {
669 if (!node)
670 return;
671
672 // Store position for this node
673 node_positions_[node] = wxPoint(x, y);
674
675 // Layout children
676 std::vector<EmberCore::Node *> children = node->GetAllChildren();
677 if (!children.empty()) {
678 int childY = y + level_spacing_;
679 int totalWidth = (children.size() - 1) * node_spacing_x_;
680 int startX = x - totalWidth / 2;
681
682 for (size_t i = 0; i < children.size(); ++i) {
683 int childX = startX + i * node_spacing_x_;
684 LayoutNodeVertical(children[i], childX, childY, level + 1);
685 }
686 }
687}
688
689// Drawing helper methods
691 wxRect clientRect = GetClientRect();
692 dc.SetBrush(wxBrush(background_color_));
693 dc.SetPen(wxPen(background_color_));
694 dc.DrawRectangle(clientRect);
695}
696
697void TreeWidget::DrawGrid(wxDC &dc) {
698 wxRect clientRect = GetClientRect();
699
700 // Get grid background color from preferences
702 const auto &mainPanelSettings = prefs.GetMainPanelSettings();
703 wxColour gridBgColor(mainPanelSettings.gridBackgroundColor.r, mainPanelSettings.gridBackgroundColor.g,
704 mainPanelSettings.gridBackgroundColor.b);
705
706 // Draw grid background
707 dc.SetBrush(wxBrush(gridBgColor));
708 dc.SetPen(*wxTRANSPARENT_PEN);
709 dc.DrawRectangle(clientRect);
710
711 // Draw grid lines
712 dc.SetPen(wxPen(grid_color_, 1, wxPENSTYLE_DOT));
713 dc.SetBrush(*wxTRANSPARENT_BRUSH);
714
715 // Draw vertical lines
716 for (int x = 0; x < clientRect.width; x += GRID_SIZE) {
717 dc.DrawLine(x, 0, x, clientRect.height);
718 }
719
720 // Draw horizontal lines
721 for (int y = 0; y < clientRect.height; y += GRID_SIZE) {
722 dc.DrawLine(0, y, clientRect.width, y);
723 }
724}
725
727 if (!behavior_tree_)
728 return;
729
730 dc.SetPen(wxPen(connection_color_, connection_width_));
731
732 for (const auto &pair : node_widgets_) {
733 EmberCore::Node *node = pair.first;
734 EmberForge::NodeWidget *parentWidget = pair.second.get();
735
736 // Draw connections to children
737 for (EmberCore::Node *child : node->GetAllChildren()) {
738 NodeWidget *childWidget = GetNodeWidget(child);
739 if (childWidget) {
740 DrawConnection(dc, parentWidget, childWidget);
741 }
742 }
743 }
744}
745
746void TreeWidget::DrawConnection(wxDC &dc, NodeWidget *parent, NodeWidget *child) {
747 if (!parent || !child)
748 return;
749
750 wxRect parentRect = parent->GetRect();
751 wxRect childRect = child->GetRect();
752 wxPoint parentCenter(parentRect.x + parentRect.width / 2, parentRect.y + parentRect.height / 2);
753 wxPoint childCenter(childRect.x + childRect.width / 2, childRect.y + childRect.height / 2);
754
755 // Draw simple line
756 dc.DrawLine(parentCenter, childCenter);
757}
758
760 if (is_selecting_) {
761 dc.SetPen(wxPen(wxColour(0, 0, 255), 1, wxPENSTYLE_DOT));
762 dc.SetBrush(wxBrush(wxColour(0, 0, 255, 50)));
763 dc.DrawRectangle(selection_rect_);
764 }
765}
766
767// Node widget management
769 if (!behavior_tree_)
770 return;
771
772 std::vector<EmberCore::Node *> allNodes = behavior_tree_->GetAllNodes();
773 for (EmberCore::Node *node : allNodes) {
774 auto widget = CreateNodeWidget(node);
775 if (widget) {
776 node_widgets_[node] = std::move(widget);
777 }
778 }
779}
780
782 for (auto &pair : node_widgets_) {
783 pair.second->UpdateFromNode();
784 }
785}
786
788
789std::unique_ptr<EmberForge::NodeWidget> TreeWidget::CreateNodeWidget(EmberCore::Node *node) {
790 if (!node)
791 return nullptr;
792
793 auto widget = std::make_unique<NodeWidget>(this, node, node_render_style_);
794
795 // Set up callbacks
796 widget->SetNodeClickCallback([this](EmberForge::NodeWidget *w, EmberCore::Node *n) { OnNodeClicked(w, n); });
797
798 widget->SetNodeDoubleClickCallback(
800
801 widget->SetNodeDragCallback([this](NodeWidget *w, const wxPoint &delta) { OnNodeDragged(w, delta); });
802
803 widget->SetNodeEditCallback([this](NodeWidget *w, const wxString &newName) { OnNodeEdited(w, newName); });
804
805 return widget;
806}
807
808// Hit testing
809NodeWidget *TreeWidget::HitTestNode(const wxPoint &point) const {
810 for (const auto &pair : node_widgets_) {
811 if (pair.second->GetRect().Contains(point)) {
812 return pair.second.get();
813 }
814 }
815 return nullptr;
816}
817
818EmberCore::Node *TreeWidget::HitTestConnection(const wxPoint &point) const {
819 // Placeholder implementation
820 return nullptr;
821}
822
823// Selection helpers
825 for (auto &pair : node_widgets_) {
826 bool selected = std::find(selected_nodes_.begin(), selected_nodes_.end(), pair.first) != selected_nodes_.end();
827 pair.second->SetSelected(selected);
828 }
829 Refresh();
830}
831
837
838// Layout helpers
840
842 auto it = node_positions_.find(node);
843 if (it != node_positions_.end()) {
844 return it->second;
845 }
846 return wxPoint(0, 0);
847}
848
850 for (auto &pair : node_widgets_) {
851 EmberCore::Node *node = pair.first;
852 EmberForge::NodeWidget *widget = pair.second.get();
853
854 wxPoint pos = CalculateNodePosition(node);
855 wxSize size = widget->GetPreferredSize();
856
857 // Apply zoom
858 pos.x = static_cast<int>(pos.x * zoom_level_);
859 pos.y = static_cast<int>(pos.y * zoom_level_);
860 size.x = static_cast<int>(size.x * zoom_level_);
861 size.y = static_cast<int>(size.y * zoom_level_);
862
863 widget->SetPosition(pos);
864 widget->SetSize(size);
865 }
866}
867
869 wxSize virtualSize = CalculateTreeSize();
870
871 virtualSize.x = static_cast<int>(virtualSize.x * zoom_level_);
872 virtualSize.y = static_cast<int>(virtualSize.y * zoom_level_);
873
874 SetVirtualSize(virtualSize);
875 SetScrollRate(20, 20);
876}
877
878// Coordinate conversion
879wxPoint TreeWidget::TreeToScreen(const wxPoint &tree_point) const {
880 wxPoint screenPoint = tree_point;
881 screenPoint.x = static_cast<int>(screenPoint.x * zoom_level_) - view_offset_.x;
882 screenPoint.y = static_cast<int>(screenPoint.y * zoom_level_) - view_offset_.y;
883 return screenPoint;
884}
885
886wxPoint TreeWidget::ScreenToTree(const wxPoint &screen_point) const {
887 wxPoint treePoint = screen_point;
888 treePoint.x = static_cast<int>((treePoint.x + view_offset_.x) / zoom_level_);
889 treePoint.y = static_cast<int>((treePoint.y + view_offset_.y) / zoom_level_);
890 return treePoint;
891}
892
893wxRect TreeWidget::TreeToScreen(const wxRect &tree_rect) const {
894 wxPoint topLeft = TreeToScreen(tree_rect.GetTopLeft());
895 wxPoint bottomRight = TreeToScreen(tree_rect.GetBottomRight());
896 return wxRect(topLeft, bottomRight);
897}
898
899wxRect TreeWidget::ScreenToTree(const wxRect &screen_rect) const {
900 wxPoint topLeft = ScreenToTree(screen_rect.GetTopLeft());
901 wxPoint bottomRight = ScreenToTree(screen_rect.GetBottomRight());
902 return wxRect(topLeft, bottomRight);
903}
904
905// Private helper methods
907 SetBackgroundStyle(wxBG_STYLE_CUSTOM);
911}
912
914 // Event handlers are set up in the event table
915}
916
918
919void TreeWidget::ShowContextMenu(const wxPoint &position) {
920 wxMenu menu;
921
922 if (!selected_nodes_.empty()) {
923 menu.Append(wxID_ANY, "Delete Selected", "Delete selected nodes");
924 menu.Append(wxID_ANY, "Duplicate Selected", "Duplicate selected nodes");
925 menu.AppendSeparator();
926 }
927
928 menu.Append(wxID_ANY, "Add Node", "Add a new node");
929 menu.AppendSeparator();
930 menu.Append(wxID_ANY, "Zoom to Fit", "Zoom to fit all nodes");
931 menu.Append(wxID_ANY, "Reset Zoom", "Reset zoom to 100%");
932
933 PopupMenu(&menu, position);
934}
935
936// Node widget event handlers
938 // Already handled in mouse events
939}
940
942 if (edit_callback_) {
943 edit_callback_(node, "name", node->GetName());
944 }
945}
946
947void TreeWidget::OnNodeDragged(NodeWidget *widget, const wxPoint &delta) {
949 // Update position for manual layout
950 wxPoint currentPos = widget->GetPosition();
951 widget->SetPosition(currentPos + delta);
952 }
953}
954
955void TreeWidget::OnNodeEdited(EmberForge::NodeWidget *widget, const wxString &new_name) {
956 EmberCore::Node *node = widget->GetNode();
957 if (node && edit_callback_) {
958 edit_callback_(node, "name", new_name);
959 }
960}
961
964 const auto &btViewSettings = prefs.GetBehaviorTreeViewSettings();
965
966 // Load zoom/pan preferences
967 zoom_step_ = btViewSettings.zoomStepSize;
968 mouse_wheel_sensitivity_ = btViewSettings.mouseWheelSensitivity;
969 pan_sensitivity_ = btViewSettings.panSensitivity;
970 zoom_follows_cursor_ = btViewSettings.zoomFollowsCursor;
971
972 LOG_INFO("TreeWidget",
973 wxString::Format(
974 "Loaded preferences: zoom_step=%.2f, wheel_sens=%.2f, pan_sens=%.2f, zoom_follows_cursor=%s",
976 .ToStdString());
977}
978
979} // namespace EmberForge
BehaviorTreeProjectDialog::OnProjectNameChanged BehaviorTreeProjectDialog::OnRemoveFiles wxEND_EVENT_TABLE() BehaviorTreeProjectDialog
#define LOG_INFO(category, message)
Definition Logger.h:114
Represents a node in a behavior tree structure.
Definition Node.h:20
size_t GetId() const
Definition Node.h:80
const String & GetName() const
Definition Node.h:81
std::vector< Node * > GetAllChildren()
Definition Node.cpp:304
static AppPreferencesManager & GetInstance()
BehaviorTreeViewSettings & GetBehaviorTreeViewSettings()
MainPanelSettings & GetMainPanelSettings()
Custom widget for rendering and interacting with behavior tree nodes.
Definition NodeWidget.h:16
EmberCore::Node * GetNode() const
Definition NodeWidget.h:54
RenderStyle
Node rendering styles.
Definition NodeWidget.h:21
wxSize GetPreferredSize() const
Custom widget for rendering and interacting with complete behavior trees.
Definition TreeWidget.h:19
void OnNodeDragged(EmberForge::NodeWidget *widget, const wxPoint &delta)
std::vector< EmberForge::NodeWidget * > GetSelectedNodeWidgets() const
std::unique_ptr< EmberForge::NodeWidget > CreateNodeWidget(EmberCore::Node *node)
std::vector< EmberForge::NodeWidget * > GetAllNodeWidgets() const
std::vector< EmberCore::Node * > selected_nodes_
Definition TreeWidget.h:197
void SetGridVisible(bool visible)
void HighlightExecutionPath(const std::vector< EmberCore::Node * > &path)
void SetBackgroundColor(const wxColour &color)
void DrawGrid(wxDC &dc)
virtual void OnMouseUp(wxMouseEvent &event)
NodeEditCallback edit_callback_
Definition TreeWidget.h:239
InteractionMode interaction_mode_
Definition TreeWidget.h:193
EmberCore::Node * GetSelectedNode() const
void SetLayoutAlgorithm(LayoutAlgorithm algorithm)
void DrawSelectionRect(wxDC &dc)
bool SaveLayout(const wxString &filename) const
virtual void OnRightClick(wxContextMenuEvent &event)
virtual void OnPaint(wxPaintEvent &event)
InteractionMode
Tree interaction modes.
Definition TreeWidget.h:35
void SetConnectionsVisible(bool visible)
void SetInteractionMode(InteractionMode mode)
virtual ~TreeWidget()
Destructor.
EmberForge::NodeWidget::RenderStyle node_render_style_
Definition TreeWidget.h:203
void SetZoomLevel(double zoom)
void SelectNode(EmberCore::Node *node, bool add_to_selection=false)
void SetNodeRenderStyle(EmberForge::NodeWidget::RenderStyle style)
virtual void OnChar(wxKeyEvent &event)
virtual void OnEraseBackground(wxEraseEvent &event)
std::unordered_map< EmberCore::Node *, wxPoint > node_positions_
Definition TreeWidget.h:231
virtual void OnMouseMove(wxMouseEvent &event)
void ShowContextMenu(const wxPoint &position)
bool CanAddNode(EmberCore::Node *parent) const
void LayoutNodeVertical(EmberCore::Node *node, int x, int y, int level)
bool CanMoveNode(EmberCore::Node *node, EmberCore::Node *new_parent) const
virtual void OnMouseWheel(wxMouseEvent &event)
static constexpr double MAX_ZOOM
Definition TreeWidget.h:256
bool AddNode(std::unique_ptr< EmberCore::Node > node, EmberCore::Node *parent)
void OnNodeEdited(EmberForge::NodeWidget *widget, const wxString &new_name)
void OnNodeClicked(EmberForge::NodeWidget *widget, EmberCore::Node *node)
wxPoint TreeToScreen(const wxPoint &tree_point) const
EmberForge::NodeWidget * GetNodeWidget(EmberCore::Node *node) const
static constexpr double MIN_ZOOM
Definition TreeWidget.h:255
void AnimateToNode(EmberCore::Node *node)
bool LoadLayout(const wxString &filename)
virtual void OnSize(wxSizeEvent &event)
void DeselectNode(EmberCore::Node *node)
LayoutAlgorithm layout_algorithm_
Definition TreeWidget.h:192
bool MoveNode(EmberCore::Node *node, EmberCore::Node *new_parent)
void DrawConnection(wxDC &dc, EmberForge::NodeWidget *parent, EmberForge::NodeWidget *child)
EmberForge::NodeWidget * HitTestNode(const wxPoint &point) const
virtual void OnMouseDown(wxMouseEvent &event)
wxPoint ScreenToTree(const wxPoint &screen_point) const
EmberCore::Node * HitTestConnection(const wxPoint &point) const
void SetBehaviorTree(std::shared_ptr< EmberCore::BehaviorTree > tree)
wxBitmap ExportAsImage(const wxSize &size=wxDefaultSize) const
void CenterOnNode(EmberCore::Node *node)
static const int GRID_SIZE
Definition TreeWidget.h:260
NodeSelectionCallback selection_callback_
Definition TreeWidget.h:238
void DrawBackground(wxDC &dc)
wxPoint CalculateNodePosition(EmberCore::Node *node) const
LayoutAlgorithm
Tree layout algorithms.
Definition TreeWidget.h:24
void OnNodeDoubleClicked(EmberForge::NodeWidget *widget, EmberCore::Node *node)
std::shared_ptr< EmberCore::BehaviorTree > behavior_tree_
Definition TreeWidget.h:191
virtual void OnKeyDown(wxKeyEvent &event)
std::vector< EmberCore::Node * > highlighted_path_
Definition TreeWidget.h:235
void DrawConnections(wxDC &dc)
std::vector< EmberCore::Node * > GetSelectedNodes() const
TreeChangeCallback tree_change_callback_
Definition TreeWidget.h:240
void SetConnectionStyle(int line_width, const wxColour &color)
std::unordered_map< EmberCore::Node *, std::unique_ptr< EmberForge::NodeWidget > > node_widgets_
Definition TreeWidget.h:196
wxSize CalculateTreeSize() const
Main types header for EmberCore.
wxBEGIN_EVENT_TABLE(LogTab, wxPanel) EVT_CHOICE(ID_LEVEL_FILTER
PerformancePanel::OnUpdateTimer EVT_PAINT(PerformancePanel::OnPaint) EVT_SIZE(PerformancePanel