Ember
Loading...
Searching...
No Matches
NodeWidget.cpp
Go to the documentation of this file.
3#include "Utils/Logger.h"
4#include <wx/bitmap.h>
5#include <wx/dc.h>
6#include <wx/menu.h>
7#include <wx/textctrl.h>
8#include <wx/timer.h>
9
10namespace EmberForge {
11
12// Event table
23 EVT_TIMER(wxID_ANY,
25
26 EmberForge::NodeWidget::NodeWidget(wxWindow *parent, EmberCore::Node *node,
27 RenderStyle style)
28 : wxPanel(parent, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL | wxBORDER_NONE), node_(node),
29 render_style_(style), interaction_state_(InteractionState::Normal), custom_size_(wxDefaultSize),
30 is_draggable_(true), is_highlighted_(false), animation_timer_(nullptr), animation_phase_(0.0),
31 is_animating_(false), edit_control_(nullptr) {
32 InitializeWidget();
33}
34
42
44 node_ = node;
46 Refresh();
47}
48
50 render_style_ = style;
51 Refresh();
52}
53
55 if (interaction_state_ != state) {
56 interaction_state_ = state;
58 Refresh();
59 }
60}
61
65
74
75void EmberForge::NodeWidget::StopEditing(bool save_changes) {
77 return;
78 }
79
80 if (save_changes && node_) {
81 wxString newName = edit_control_->GetValue();
82 if (newName != original_name_ && edit_callback_) {
83 edit_callback_(this, newName);
84 }
85 }
86
89 Refresh();
90}
91
93 if (!node_) {
94 return;
95 }
96
97 // Update colors based on node state
99
100 // Update size if needed
101 wxSize preferredSize = GetPreferredSize();
102 if (GetSize() != preferredSize) {
103 SetSize(preferredSize);
104 }
105}
106
108
110 if (custom_size_ != wxDefaultSize) {
111 return custom_size_;
112 }
113
114 wxSize size(MIN_WIDTH, MIN_HEIGHT);
115
116 if (node_) {
117 wxString text = GetDisplayText();
118 if (!text.IsEmpty()) {
119 wxClientDC dc(const_cast<EmberForge::NodeWidget *>(this));
120 dc.SetFont(node_font_);
121 wxSize textSize = dc.GetTextExtent(text);
122
123 size.x = std::max(size.x, textSize.x + 2 * PADDING + ICON_SIZE + PADDING);
124 size.y = std::max(size.y, textSize.y + 2 * PADDING);
125 }
126 }
127
128 return size;
129}
130
132
134 custom_size_ = size;
135 SetSize(size);
136}
137
138void EmberForge::NodeWidget::SetNodeColors(const wxColour &background, const wxColour &border, const wxColour &text) {
139 background_color_ = background;
140 border_color_ = border;
141 text_color_ = text;
142 Refresh();
143}
144
145void EmberForge::NodeWidget::SetNodeFont(const wxFont &font) {
146 node_font_ = font;
147 Refresh();
148}
149
157
159 if (!animation_timer_) {
160 animation_timer_ = new wxTimer(this);
161 }
162
163 is_animating_ = true;
164 animation_phase_ = 0.0;
165 animation_timer_->Start(50); // 20 FPS
166}
167
169 if (animation_timer_) {
170 animation_timer_->Stop();
171 }
172 is_animating_ = false;
173 Refresh();
174}
175
177 if (is_highlighted_ != highlighted) {
178 is_highlighted_ = highlighted;
179 UpdateColors();
180 Refresh();
181 }
182}
183
184// Protected event handlers
185void EmberForge::NodeWidget::OnPaint(wxPaintEvent &event) {
186 wxPaintDC dc(this);
187
188 // Clear background
189 dc.SetBackground(wxBrush(GetBackgroundColor()));
190 dc.Clear();
191
192 wxRect clientRect = GetClientRect();
193
194 // Draw node background
195 DrawNodeBackground(dc, clientRect);
196
197 // Draw node border
198 DrawNodeBorder(dc, clientRect);
199
200 // Draw content based on render style
201 switch (render_style_) {
204 break;
208 break;
213 break;
219 break;
220 }
221}
222
223void EmberForge::NodeWidget::OnSize(wxSizeEvent &event) {
224 // Reposition edit control if active
225 if (edit_control_) {
226 edit_control_->SetSize(GetTextRect());
227 }
228
229 event.Skip();
230}
231
233 // Prevent flicker by not erasing background
234}
235
242
249
250void EmberForge::NodeWidget::OnMouseDown(wxMouseEvent &event) {
251 if (event.LeftDown()) {
252 drag_start_pos_ = event.GetPosition();
253 last_mouse_pos_ = event.GetPosition();
254
255 if (click_callback_) {
256 click_callback_(this, node_);
257 }
258
259 if (!IsSelected()) {
260 SetSelected(true);
261 }
262
263 CaptureMouse();
264 }
265 event.Skip();
266}
267
268void EmberForge::NodeWidget::OnMouseUp(wxMouseEvent &event) {
269 if (HasCapture()) {
270 ReleaseMouse();
271 }
272
275 }
276
277 event.Skip();
278}
279
280void EmberForge::NodeWidget::OnMouseMove(wxMouseEvent &event) {
281 if (event.Dragging() && HasCapture() && is_draggable_) {
282 wxPoint currentPos = event.GetPosition();
283 wxPoint delta = currentPos - last_mouse_pos_;
284
287 }
288
289 if (drag_callback_) {
290 drag_callback_(this, delta);
291 }
292
293 last_mouse_pos_ = currentPos;
294 }
295 event.Skip();
296}
297
301 } else {
302 // Default behavior: start editing
303 StartEditing();
304 }
305 event.Skip();
306}
307
308void EmberForge::NodeWidget::OnRightClick(wxContextMenuEvent &event) { ShowContextMenu(event.GetPosition()); }
309
310void EmberForge::NodeWidget::OnKeyDown(wxKeyEvent &event) {
311 int keyCode = event.GetKeyCode();
312
313 switch (keyCode) {
314 case WXK_F2:
315 StartEditing();
316 break;
317 case WXK_ESCAPE:
319 StopEditing(false);
320 }
321 break;
322 case WXK_RETURN:
324 StopEditing(true);
325 }
326 break;
327 default:
328 event.Skip();
329 break;
330 }
331}
332
333void EmberForge::NodeWidget::OnChar(wxKeyEvent &event) { event.Skip(); }
334
335void EmberForge::NodeWidget::OnSetFocus(wxFocusEvent &event) {
336 Refresh();
337 event.Skip();
338}
339
340void EmberForge::NodeWidget::OnKillFocus(wxFocusEvent &event) {
342 StopEditing(true);
343 }
344 Refresh();
345 event.Skip();
346}
347
348// Drawing helpers
349void EmberForge::NodeWidget::DrawNodeBackground(wxDC &dc, const wxRect &rect) {
350 wxColour bgColor = GetBackgroundColor();
351
352 if (is_animating_) {
353 // Pulse effect
354 int alpha = 128 + static_cast<int>(127 * std::sin(animation_phase_));
355 bgColor = wxColour(bgColor.Red(), bgColor.Green(), bgColor.Blue(), alpha);
356 }
357
358 dc.SetBrush(wxBrush(bgColor));
359 dc.SetPen(wxPen(bgColor));
360 dc.DrawRoundedRectangle(rect, 4);
361}
362
363void EmberForge::NodeWidget::DrawNodeBorder(wxDC &dc, const wxRect &rect) {
364 wxColour borderColor = GetBorderColor();
365
366 dc.SetBrush(wxNullBrush);
367 dc.SetPen(wxPen(borderColor, BORDER_WIDTH));
368 dc.DrawRoundedRectangle(rect, 4);
369}
370
371void EmberForge::NodeWidget::DrawNodeIcon(wxDC &dc, const wxRect &icon_rect) {
372 wxBitmap icon = GetNodeIcon();
373 if (icon.IsOk()) {
374 dc.DrawBitmap(icon, icon_rect.GetTopLeft());
375 }
376}
377
378void EmberForge::NodeWidget::DrawNodeText(wxDC &dc, const wxRect &text_rect) {
379 wxString text = GetDisplayText();
380 if (!text.IsEmpty()) {
381 dc.SetFont(node_font_);
382 dc.SetTextForeground(GetTextColor());
383 dc.DrawLabel(text, text_rect, wxALIGN_CENTER);
384 }
385}
386
387void EmberForge::NodeWidget::DrawNodeStatus(wxDC &dc, const wxRect &status_rect) {
388 if (!node_)
389 return;
390
391 // Draw status indicator based on node execution state
392 EmberCore::Node::Status status = node_->GetStatus();
393 wxColour statusColor;
394
395 switch (status) {
397 statusColor = wxColour(0, 255, 0); // Green
398 break;
400 statusColor = wxColour(255, 0, 0); // Red
401 break;
403 statusColor = wxColour(255, 255, 0); // Yellow
404 break;
405 default:
406 statusColor = wxColour(128, 128, 128); // Gray
407 break;
408 }
409
410 dc.SetBrush(wxBrush(statusColor));
411 dc.SetPen(wxPen(statusColor));
412 wxPoint center(status_rect.x + status_rect.width / 2, status_rect.y + status_rect.height / 2);
413 dc.DrawCircle(center, 4);
414}
415
417 std::vector<wxPoint> points = GetConnectionPoints();
418
419 dc.SetBrush(wxBrush(wxColour(0, 0, 255))); // Blue
420 dc.SetPen(wxPen(wxColour(0, 0, 255)));
421
422 for (const wxPoint &point : points) {
423 dc.DrawCircle(point, 3);
424 }
425}
426
427// Layout helpers
429 wxRect rect = GetClientRect();
430 rect.Deflate(PADDING);
431 return rect;
432}
433
436 return wxRect();
437 }
438
439 wxRect contentRect = GetContentRect();
440 return wxRect(contentRect.x, contentRect.y, ICON_SIZE, ICON_SIZE);
441}
442
444 wxRect contentRect = GetContentRect();
445
447 return contentRect;
448 }
449
450 wxRect iconRect = GetIconRect();
451 return wxRect(iconRect.GetRight() + PADDING, contentRect.y, contentRect.width - iconRect.width - PADDING,
452 contentRect.height);
453}
454
457 return wxRect();
458 }
459
460 wxRect contentRect = GetContentRect();
461 return wxRect(contentRect.GetRight() - 10, contentRect.y, 10, 10);
462}
463
465 std::vector<wxPoint> points;
466 wxRect rect = GetClientRect();
467
468 // Top center
469 points.push_back(wxPoint(rect.width / 2, 0));
470 // Bottom center
471 points.push_back(wxPoint(rect.width / 2, rect.height));
472 // Left center
473 points.push_back(wxPoint(0, rect.height / 2));
474 // Right center
475 points.push_back(wxPoint(rect.width, rect.height / 2));
476
477 return points;
478}
479
480// State helpers
482 switch (interaction_state_) {
484 return wxColour(240, 240, 255);
486 return wxColour(200, 200, 255);
488 return wxColour(255, 255, 200);
490 return wxColour(255, 240, 240);
491 default:
492 return wxColour(255, 255, 255);
493 }
494}
495
497 if (is_highlighted_) {
498 return wxColour(255, 165, 0); // Orange
499 }
500
501 // Get colors from preferences
503 const auto &mainPanelSettings = prefs.GetMainPanelSettings();
504
505 switch (interaction_state_) {
507 return wxColour(mainPanelSettings.selectedNodeColor.r, mainPanelSettings.selectedNodeColor.g,
508 mainPanelSettings.selectedNodeColor.b);
510 return wxColour(mainPanelSettings.hoveredNodeColor.r, mainPanelSettings.hoveredNodeColor.g,
511 mainPanelSettings.hoveredNodeColor.b);
513 return wxColour(255, 165, 0); // Orange
514 default:
515 return wxColour(128, 128, 128); // Gray
516 }
517}
518
520 // Get text colors from preferences
522 const auto &mainPanelSettings = prefs.GetMainPanelSettings();
523
524 switch (interaction_state_) {
526 return wxColour(mainPanelSettings.selectedNodeTextColor.r, mainPanelSettings.selectedNodeTextColor.g,
527 mainPanelSettings.selectedNodeTextColor.b);
529 return wxColour(mainPanelSettings.hoveredNodeTextColor.r, mainPanelSettings.hoveredNodeTextColor.g,
530 mainPanelSettings.hoveredNodeTextColor.b);
531 default:
532 return wxColour(0, 0, 0); // Black
533 }
534}
535
537 // Return appropriate icon based on node type
538 // This is a placeholder - actual implementation would load from resources
539 return wxNullBitmap;
540}
541
543 if (!node_) {
544 return "No EmberCore::Node";
545 }
546
547 return node_->GetName();
548}
549
550// Animation helpers
552
554 if (is_animating_) {
555 animation_phase_ += 0.2; // Animation speed
556 if (animation_phase_ > 2 * M_PI) {
557 animation_phase_ -= 2 * M_PI;
558 }
559 Refresh();
560 }
561}
562
563// Private helper methods
565 // Set default colors
567
568 // Set minimum size
569 SetMinSize(CalculateMinSize());
570
571 // Enable double buffering to reduce flicker
572 SetBackgroundStyle(wxBG_STYLE_CUSTOM);
573}
574
576 // Colors are computed dynamically, no storage needed
577}
578
580 if (edit_control_ || !node_) {
581 return;
582 }
583
584 original_name_ = node_->GetName();
585
587 new wxTextCtrl(this, wxID_ANY, original_name_, wxDefaultPosition, wxDefaultSize, wxTE_PROCESS_ENTER);
588
589 edit_control_->SetSize(GetTextRect());
590 edit_control_->SetFocus();
591 edit_control_->SelectAll();
592
593 // Bind events
594 edit_control_->Bind(wxEVT_TEXT_ENTER, [this](wxCommandEvent &) { StopEditing(true); });
595
596 edit_control_->Bind(wxEVT_KILL_FOCUS, [this](wxFocusEvent &event) {
597 StopEditing(true);
598 event.Skip();
599 });
600}
601
603 if (edit_control_) {
604 edit_control_->Destroy();
605 edit_control_ = nullptr;
606 }
607}
608
609void EmberForge::NodeWidget::ShowContextMenu(const wxPoint &position) {
610 wxMenu menu;
611
612 menu.Append(wxID_ANY, "Edit", "Edit node name");
613 menu.Append(wxID_ANY, "Delete", "Delete this node");
614 menu.AppendSeparator();
615 menu.Append(wxID_ANY, "Properties", "Show node properties");
616
617 PopupMenu(&menu, position);
618}
619
620} // namespace EmberForge
BehaviorTreeProjectDialog::OnProjectNameChanged BehaviorTreeProjectDialog::OnRemoveFiles wxEND_EVENT_TABLE() BehaviorTreeProjectDialog
Represents a node in a behavior tree structure.
Definition Node.h:20
Status
Node status for runtime execution tracking.
Definition Node.h:37
static AppPreferencesManager & GetInstance()
MainPanelSettings & GetMainPanelSettings()
Custom widget for rendering and interacting with behavior tree nodes.
Definition NodeWidget.h:16
virtual void OnMouseMove(wxMouseEvent &event)
virtual void OnKillFocus(wxFocusEvent &event)
InteractionState
Node interaction states.
Definition NodeWidget.h:31
static const int ICON_SIZE
Definition NodeWidget.h:200
void DrawNodeBackground(wxDC &dc, const wxRect &rect)
void DrawNodeText(wxDC &dc, const wxRect &text_rect)
virtual void OnMouseLeave(wxMouseEvent &event)
virtual void OnSetFocus(wxFocusEvent &event)
wxRect GetIconRect() const
void SetNodeFont(const wxFont &font)
virtual void OnPaint(wxPaintEvent &event)
void DrawConnectionPoints(wxDC &dc)
NodeEditCallback edit_callback_
Definition NodeWidget.h:188
wxTextCtrl * edit_control_
Definition NodeWidget.h:181
void SetInteractionState(InteractionState state)
static const int MIN_WIDTH
Definition NodeWidget.h:201
wxBitmap GetNodeIcon() const
static const int PADDING
Definition NodeWidget.h:199
void OnAnimationTimer(wxTimerEvent &event)
void DrawNodeIcon(wxDC &dc, const wxRect &icon_rect)
void ShowContextMenu(const wxPoint &position)
void SetNode(EmberCore::Node *node)
virtual void OnEraseBackground(wxEraseEvent &event)
NodeClickCallback click_callback_
Definition NodeWidget.h:185
virtual void OnSize(wxSizeEvent &event)
wxSize CalculateMinSize() const
virtual ~NodeWidget()
Destructor.
wxColour GetTextColor() const
virtual void OnKeyDown(wxKeyEvent &event)
static const int MIN_HEIGHT
Definition NodeWidget.h:202
virtual void OnMouseEnter(wxMouseEvent &event)
virtual void OnRightClick(wxContextMenuEvent &event)
void SetSelected(bool selected)
InteractionState interaction_state_
Definition NodeWidget.h:160
void SetNodeColors(const wxColour &background, const wxColour &border, const wxColour &text)
static const int BORDER_WIDTH
Definition NodeWidget.h:198
void DrawNodeStatus(wxDC &dc, const wxRect &status_rect)
wxRect GetStatusRect() const
virtual void OnChar(wxKeyEvent &event)
wxRect GetTextRect() const
RenderStyle
Node rendering styles.
Definition NodeWidget.h:21
void StopEditing(bool save_changes=true)
NodeDragCallback drag_callback_
Definition NodeWidget.h:187
bool IsSelected() const
Definition NodeWidget.h:65
wxSize GetPreferredSize() const
wxString GetDisplayText() const
NodeDoubleClickCallback double_click_callback_
Definition NodeWidget.h:186
wxColour GetBackgroundColor() const
virtual void OnMouseDoubleClick(wxMouseEvent &event)
EmberCore::Node * node_
Definition NodeWidget.h:158
std::vector< wxPoint > GetConnectionPoints() const
virtual void OnMouseUp(wxMouseEvent &event)
RenderStyle render_style_
Definition NodeWidget.h:159
wxColour GetBorderColor() const
void SetHighlighted(bool highlighted)
wxRect GetContentRect() const
void SetRenderStyle(RenderStyle style)
virtual void OnMouseDown(wxMouseEvent &event)
void DrawNodeBorder(wxDC &dc, const wxRect &rect)
void SetCustomSize(const wxSize &size)
Main types header for EmberCore.
wxBEGIN_EVENT_TABLE(LogTab, wxPanel) EVT_CHOICE(ID_LEVEL_FILTER
PerformancePanel::OnUpdateTimer EVT_PAINT(PerformancePanel::OnPaint) EVT_SIZE(PerformancePanel