Olympe Engine 2.0
2D Game Engine with ECS Architecture
Loading...
Searching...
No Matches
NodeSearchPanel.cpp
Go to the documentation of this file.
1/**
2 * @file NodeSearchPanel.cpp
3 * @brief NodeSearchPanel implementation (Phase 9).
4 * @author Olympe Engine
5 * @date 2026-03-09
6 */
7
8#include "NodeSearchPanel.h"
9
10#include <algorithm>
11#include <cctype>
12
13#include "../system/system_utils.h"
14
15namespace Olympe {
16
17// ============================================================================
18// Helpers
19// ============================================================================
20
21namespace {
22
23std::string ToLowerNSP(const std::string& s)
24{
25 std::string out = s;
26 for (size_t i = 0; i < out.size(); ++i)
27 out[i] = static_cast<char>(std::tolower(static_cast<unsigned char>(out[i])));
28 return out;
29}
30
31} // anonymous namespace
32
33// ============================================================================
34// Singleton
35// ============================================================================
36
38{
39 static NodeSearchPanel s_Instance;
40 return s_Instance;
41}
42
44 : m_IsOpen(false)
45 , m_SpawnPosX(0.0f)
46 , m_SpawnPosY(0.0f)
47 , m_SelectedIndex(-1)
48{
50}
51
52// ============================================================================
53// Catalogue initialisation
54// ============================================================================
55
57{
58 m_Catalogue.clear();
59
60 auto add = [this](const std::string& type,
61 const std::string& name,
62 const std::string& category,
63 const std::string& desc)
64 {
66 r.nodeType = type;
67 r.name = name;
68 r.category = category;
69 r.description = desc;
70 r.relevanceScore = 0.0f;
71 m_Catalogue.push_back(r);
72 };
73
74 // ---- Control Flow ----
75 add("EntryPoint", "Entry Point", "ControlFlow", "Graph execution start node.");
76 add("Sequence", "Sequence", "ControlFlow", "Executes children left-to-right.");
77 add("Selector", "Selector", "ControlFlow", "Executes children until one succeeds.");
78 add("Branch", "Branch", "ControlFlow", "Conditional fork (true/false).");
79 add("Repeat", "Repeat", "ControlFlow", "Loops a subgraph N times.");
80 add("Wait", "Wait", "ControlFlow", "Pauses execution for a duration.");
81 add("SubGraph", "Sub Graph", "ControlFlow", "Delegates execution to an external graph.");
82
83 // ---- Actions ----
84 add("MoveToLocation", "Move To Location", "Actions", "Moves an entity to a target position.");
85 add("Attack", "Attack", "Actions", "Triggers an attack on a target.");
86 add("Flee", "Flee", "Actions", "Makes the entity flee from a threat.");
87 add("Patrol", "Patrol", "Actions", "Cycles through patrol waypoints.");
88 add("SetVariable", "Set Variable", "Actions", "Writes a value to the blackboard.");
89 add("LogMessage", "Log Message", "Actions", "Emits a debug message.");
90
91 // ---- Data (Phase 24.1) ----
92 add("Variable", "Variable", "Data", "Data pure node - reads a blackboard variable.");
93 add("MathOp", "Math Operation", "Data", "Data pure node - computes arithmetic operation.");
94 add("GetBBValue", "Get BB Value", "Data", "Reads a value from the blackboard.");
95 add("SetBBValue", "Set BB Value", "Data", "Writes a value to the blackboard.");
96
97 SYSTEM_LOG << "[NodeSearchPanel] Catalogue initialised with "
98 << static_cast<int>(m_Catalogue.size()) << " entries." << std::endl;
99}
100
101// ============================================================================
102// Open / close
103// ============================================================================
104
105void NodeSearchPanel::OpenSearch(float posX, float posY)
106{
107 m_IsOpen = true;
108 m_SpawnPosX = posX;
109 m_SpawnPosY = posY;
110 m_SelectedIndex = -1;
111 m_Results.clear();
112
113 // Show entire catalogue by default
114 UpdateQuery("");
115}
116
118{
119 m_IsOpen = false;
120 m_SelectedIndex = -1;
121 m_Results.clear();
122}
123
125{
126 return m_IsOpen;
127}
128
129// ============================================================================
130// Search
131// ============================================================================
132
133float NodeSearchPanel::ComputeScore(const std::string& query,
134 const std::string& candidate)
135{
136 if (query.empty())
137 return 1.0f;
138
139 const std::string q = ToLowerNSP(query);
140 const std::string c = ToLowerNSP(candidate);
141
142 if (c.empty())
143 return 0.0f;
144
145 // Exact match
146 if (c == q)
147 return 1.0f;
148
149 // Prefix match
150 if (c.size() >= q.size() && c.substr(0, q.size()) == q)
151 return 0.8f;
152
153 // Substring match
154 if (c.find(q) != std::string::npos)
155 return 0.6f;
156
157 // Scattered character match
158 size_t qi = 0;
159 size_t gaps = 0;
160 for (size_t ci = 0; ci < c.size() && qi < q.size(); ++ci)
161 {
162 if (c[ci] == q[qi])
163 ++qi;
164 else
165 ++gaps;
166 }
167 if (qi == q.size())
168 {
169 float score = 0.4f - static_cast<float>(gaps) * 0.01f;
170 return (score > 0.01f) ? score : 0.01f;
171 }
172
173 return 0.0f;
174}
175
176void NodeSearchPanel::UpdateQuery(const std::string& query)
177{
178 m_Results.clear();
179
180 for (size_t i = 0; i < m_Catalogue.size(); ++i)
181 {
183
184 float scoreByName = ComputeScore(query, entry.name);
185 float scoreByType = ComputeScore(query, entry.nodeType);
186 float scoreByDesc = ComputeScore(query, entry.description) * 0.5f;
187
188 float best = scoreByName;
191
192 if (best > 0.0f)
193 {
196 m_Results.push_back(r);
197 }
198 }
199
200 // Sort by relevance descending, then name ascending for stable order
201 std::sort(m_Results.begin(), m_Results.end(),
202 [](const NodeSearchResult& a, const NodeSearchResult& b) {
203 if (a.relevanceScore != b.relevanceScore)
204 return a.relevanceScore > b.relevanceScore;
205 return a.name < b.name;
206 });
207
208 // Reset selection when results change
209 m_SelectedIndex = m_Results.empty() ? -1 : 0;
210}
211
212const std::vector<NodeSearchResult>& NodeSearchPanel::GetResults() const
213{
214 return m_Results;
215}
216
217// ============================================================================
218// Selection
219// ============================================================================
220
222{
223 if (index < 0 || index >= static_cast<int>(m_Results.size()))
224 return;
225
226 const std::string& type = m_Results[static_cast<size_t>(index)].nodeType;
227 if (m_OnNodeAdd)
229
230 CloseSearch();
231}
232
234{
235 return m_SelectedIndex;
236}
237
239{
240 if (m_Results.empty())
241 return;
242 int n = static_cast<int>(m_Results.size());
243 m_SelectedIndex = (m_SelectedIndex <= 0) ? (n - 1) : (m_SelectedIndex - 1);
244}
245
247{
248 if (m_Results.empty())
249 return;
250 int n = static_cast<int>(m_Results.size());
251 m_SelectedIndex = (m_SelectedIndex >= n - 1) ? 0 : (m_SelectedIndex + 1);
252}
253
254// ============================================================================
255// Callback
256// ============================================================================
257
259 std::function<void(const std::string&, float, float)> callback)
260{
262}
263
264// ============================================================================
265// Spawn position
266// ============================================================================
267
270
271} // namespace Olympe
ComponentTypeID GetComponentTypeID_Static()
Definition ECS_Entity.h:56
Quick node-add search panel with fuzzy matching (Phase 9).
Singleton fuzzy-search panel for adding nodes to the graph.
int GetSelectedIndex() const
Returns the index of the currently highlighted result (-1 = none).
void SelectNext()
Moves the selection down by one step (wraps around).
void UpdateQuery(const std::string &query)
Recomputes the result list for the given query string.
bool IsOpen() const
Returns true if the panel is currently visible.
void SelectPrevious()
Moves the selection up by one step (wraps around).
std::vector< NodeSearchResult > m_Results
Current filtered/sorted results.
float GetSpawnX() const
Returns the X coordinate where the new node should be spawned.
std::function< void(const std::string &, float, float)> m_OnNodeAdd
void ConfirmSelection(int index)
Fires the OnNodeAdd callback for the result at index and closes the panel.
const std::vector< NodeSearchResult > & GetResults() const
Returns the current result list sorted by relevance (best first).
void OpenSearch(float posX, float posY)
Opens the search panel at the given graph-space position.
std::vector< NodeSearchResult > m_Catalogue
Full unfiltered catalogue.
void CloseSearch()
Closes the search panel and clears the query.
float GetSpawnY() const
Returns the Y coordinate where the new node should be spawned.
static float ComputeScore(const std::string &query, const std::string &candidate)
Compute a [0..1] relevance score; returns 0 if there is no match.
static NodeSearchPanel & Get()
Returns the single shared instance.
void SetNodeAddCallback(std::function< void(const std::string &, float, float)> callback)
Registers the callback that fires when the user confirms a node.
std::string ToLowerNSP(const std::string &s)
< Provides AssetID and INVALID_ASSET_ID
A single match returned by NodeSearchPalette::FuzzySearch().
std::string nodeType
Internal type key (e.g. "Sequence")
float relevanceScore
Higher = better match.
#define SYSTEM_LOG