Skip to content

Commit 685e0c9

Browse files
committed
[console] console variable suggestion will now appear in a pop-up, no need to scroll
1 parent 52b7bc8 commit 685e0c9

2 files changed

Lines changed: 144 additions & 91 deletions

File tree

source/editor/Widgets/Console.cpp

Lines changed: 129 additions & 87 deletions
Original file line numberDiff line numberDiff line change
@@ -94,11 +94,10 @@ void Console::OnTickVisible()
9494
m_log_filter.Draw("Filter", ImGui::GetContentRegionAvail().x - label_width);
9595
ImGui::Separator();
9696

97-
// calculate split sizes
97+
// calculate split sizes - no longer reserving space for autocomplete (it's a popup now)
9898
const float input_height = ImGui::GetFrameHeightWithSpacing() + ImGui::GetStyle().ItemSpacing.y;
99-
const float autocomplete_height = m_show_autocomplete ? 200.0f * spartan::Window::GetDpiScale() : 0.0f;
10099
const float available_height = ImGui::GetContentRegionAvail().y;
101-
const float log_height = available_height - input_height - autocomplete_height;
100+
const float log_height = available_height - input_height;
102101

103102
// safety first
104103
std::scoped_lock lock(m_mutex);
@@ -193,111 +192,154 @@ void Console::OnTickVisible()
193192
}
194193
}
195194

196-
if (m_show_autocomplete && !m_filtered_cvars.empty())
197-
{
198-
ImGui::Separator();
195+
// input field
196+
ImGui::Separator();
197+
ImGui::SetNextItemWidth(-1.0f);
199198

200-
static const ImGuiTableFlags table_flags =
201-
ImGuiTableFlags_RowBg |
202-
ImGuiTableFlags_BordersOuter |
203-
ImGuiTableFlags_ScrollY |
204-
ImGuiTableFlags_Resizable |
205-
ImGuiTableFlags_SizingStretchProp;
199+
ImGuiInputTextFlags input_flags =
200+
ImGuiInputTextFlags_EnterReturnsTrue |
201+
ImGuiInputTextFlags_CallbackHistory |
202+
ImGuiInputTextFlags_CallbackAlways;
206203

207-
const ImVec2 autocomplete_size = ImVec2(-1.0f, autocomplete_height);
204+
bool reclaim_focus = false;
208205

209-
if (ImGui::BeginTable("##console_autocomplete", 3, table_flags, autocomplete_size))
210-
{
211-
ImGui::TableSetupColumn("Name", ImGuiTableColumnFlags_WidthStretch, 0.4f);
212-
ImGui::TableSetupColumn("Value", ImGuiTableColumnFlags_WidthStretch, 0.2f);
213-
ImGui::TableSetupColumn("Description", ImGuiTableColumnFlags_WidthStretch, 0.4f);
214-
ImGui::TableHeadersRow();
206+
// store input field position for popup placement
207+
ImVec2 input_pos = ImGui::GetCursorScreenPos();
208+
float input_width = ImGui::GetContentRegionAvail().x;
215209

216-
for (size_t i = 0; i < m_filtered_cvars.size(); i++)
217-
{
218-
const ConsoleVariable* cvar = ConsoleRegistry::Get().Find(m_filtered_cvars[i]);
210+
if (ImGui::InputText("##console_input", m_input_buffer, IM_ARRAYSIZE(m_input_buffer), input_flags, [](ImGuiInputTextCallbackData* data) -> int
211+
{
212+
Console* console = static_cast<Console*>(data->UserData);
213+
return console->InputCallback(data);
214+
}, this))
215+
{
216+
ExecuteCommand(m_input_buffer);
217+
m_input_buffer[0] = '\0';
218+
m_show_autocomplete = false;
219+
reclaim_focus = true;
220+
}
219221

220-
ImGui::TableNextRow();
221-
ImGui::PushID(static_cast<int>(i));
222+
bool input_active = ImGui::IsItemActive();
222223

223-
bool is_selected = (std::cmp_equal(i, m_autocomplete_selection));
224-
if (is_selected)
225-
{
226-
ImGui::TableSetBgColor(ImGuiTableBgTarget_RowBg0, ImGui::GetColorU32(ImGuiCol_HeaderHovered));
227-
}
224+
if (input_active)
225+
{
226+
if (m_show_autocomplete && !m_filtered_cvars.empty())
227+
{
228+
if (ImGui::IsKeyPressed(ImGuiKey_DownArrow))
229+
{
230+
m_autocomplete_selection = (m_autocomplete_selection + 1) % static_cast<int>(m_filtered_cvars.size());
231+
}
232+
else if (ImGui::IsKeyPressed(ImGuiKey_UpArrow))
233+
{
234+
m_autocomplete_selection = (m_autocomplete_selection - 1 + static_cast<int>(m_filtered_cvars.size())) % static_cast<int>(m_filtered_cvars.size());
235+
}
236+
else if (ImGui::IsKeyPressed(ImGuiKey_Tab))
237+
{
238+
ApplyAutocomplete();
239+
reclaim_focus = true;
240+
}
241+
else if (ImGui::IsKeyPressed(ImGuiKey_Escape))
242+
{
243+
m_show_autocomplete = false;
244+
}
245+
}
246+
}
228247

229-
ImGui::TableSetColumnIndex(0);
230-
if (ImGui::Selectable(std::string(cvar->m_name).c_str(), is_selected, ImGuiSelectableFlags_SpanAllColumns | ImGuiSelectableFlags_AllowDoubleClick))
231-
{
232-
m_autocomplete_selection = static_cast<int>(i);
233-
if (ImGui::IsMouseDoubleClicked(0))
234-
{
235-
ApplyAutocomplete();
236-
}
237-
}
248+
if (ImGui::IsWindowAppearing() || reclaim_focus)
249+
{
250+
ImGui::SetKeyboardFocusHere(-1);
251+
}
238252

239-
ImGui::TableSetColumnIndex(1);
240-
std::string value_str = ConsoleRegistry::Get().GetValueAsString(cvar->m_name).value();
241-
ImGui::TextUnformatted(value_str.c_str());
253+
// autocomplete popup - rendered as a floating window above the input
254+
if (m_show_autocomplete && !m_filtered_cvars.empty())
255+
{
256+
const float popup_max_height = 250.0f * spartan::Window::GetDpiScale();
257+
const float row_height = ImGui::GetTextLineHeightWithSpacing();
258+
const float header_height = row_height + ImGui::GetStyle().ItemSpacing.y;
259+
const float content_height = std::min(popup_max_height, header_height + row_height * static_cast<float>(m_filtered_cvars.size()));
260+
261+
// position popup above the input field
262+
ImVec2 popup_pos = ImVec2(input_pos.x, input_pos.y - content_height - ImGui::GetStyle().ItemSpacing.y);
263+
264+
ImGui::SetNextWindowPos(popup_pos);
265+
ImGui::SetNextWindowSize(ImVec2(input_width, content_height));
266+
ImGui::SetNextWindowBgAlpha(0.92f);
267+
268+
ImGuiWindowFlags popup_flags =
269+
ImGuiWindowFlags_NoTitleBar |
270+
ImGuiWindowFlags_NoResize |
271+
ImGuiWindowFlags_NoMove |
272+
ImGuiWindowFlags_NoSavedSettings |
273+
ImGuiWindowFlags_NoFocusOnAppearing;
274+
275+
ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 4.0f);
276+
ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 1.0f);
277+
ImGui::PushStyleColor(ImGuiCol_Border, ImVec4(0.5f, 0.5f, 0.5f, 0.5f));
278+
279+
if (ImGui::Begin("##console_autocomplete_popup", nullptr, popup_flags))
280+
{
281+
static const ImGuiTableFlags table_flags =
282+
ImGuiTableFlags_RowBg |
283+
ImGuiTableFlags_ScrollY |
284+
ImGuiTableFlags_SizingStretchProp;
242285

243-
ImGui::TableSetColumnIndex(2);
244-
ImGui::TextWrapped("%s", std::string(cvar->m_hint).c_str());
286+
if (ImGui::BeginTable("##console_autocomplete", 3, table_flags))
287+
{
288+
ImGui::TableSetupColumn("Name", ImGuiTableColumnFlags_WidthStretch, 0.4f);
289+
ImGui::TableSetupColumn("Value", ImGuiTableColumnFlags_WidthStretch, 0.2f);
290+
ImGui::TableSetupColumn("Description", ImGuiTableColumnFlags_WidthStretch, 0.4f);
291+
ImGui::TableHeadersRow();
245292

246-
ImGui::PopID();
247-
}
293+
for (size_t i = 0; i < m_filtered_cvars.size(); i++)
294+
{
295+
const ConsoleVariable* cvar = ConsoleRegistry::Get().Find(m_filtered_cvars[i]);
248296

249-
ImGui::EndTable();
250-
}
251-
}
297+
ImGui::TableNextRow();
298+
ImGui::PushID(static_cast<int>(i));
252299

253-
{
254-
ImGui::Separator();
300+
bool is_selected = (std::cmp_equal(i, m_autocomplete_selection));
301+
if (is_selected)
302+
{
303+
ImGui::TableSetBgColor(ImGuiTableBgTarget_RowBg0, ImGui::GetColorU32(ImGuiCol_HeaderHovered));
304+
}
255305

256-
ImGui::SetNextItemWidth(-1.0f);
306+
ImGui::TableSetColumnIndex(0);
307+
if (ImGui::Selectable(std::string(cvar->m_name).c_str(), is_selected, ImGuiSelectableFlags_SpanAllColumns | ImGuiSelectableFlags_AllowDoubleClick))
308+
{
309+
m_autocomplete_selection = static_cast<int>(i);
310+
if (ImGui::IsMouseDoubleClicked(0))
311+
{
312+
ApplyAutocomplete();
313+
}
314+
}
257315

258-
ImGuiInputTextFlags input_flags =
259-
ImGuiInputTextFlags_EnterReturnsTrue |
260-
ImGuiInputTextFlags_CallbackHistory |
261-
ImGuiInputTextFlags_CallbackAlways;
316+
// scroll to keep selected item visible
317+
if (is_selected && ImGui::IsKeyPressed(ImGuiKey_DownArrow))
318+
{
319+
ImGui::SetScrollHereY(0.5f);
320+
}
321+
if (is_selected && ImGui::IsKeyPressed(ImGuiKey_UpArrow))
322+
{
323+
ImGui::SetScrollHereY(0.5f);
324+
}
262325

263-
bool reclaim_focus = false;
326+
ImGui::TableSetColumnIndex(1);
327+
std::string value_str = ConsoleRegistry::Get().GetValueAsString(cvar->m_name).value();
328+
ImGui::TextUnformatted(value_str.c_str());
264329

265-
if (ImGui::InputText("##console_input", m_input_buffer, IM_ARRAYSIZE(m_input_buffer), input_flags, [](ImGuiInputTextCallbackData* data) -> int
266-
{
267-
Console* console = static_cast<Console*>(data->UserData);
268-
return console->InputCallback(data);
269-
}, this))
270-
{
271-
ExecuteCommand(m_input_buffer);
272-
m_input_buffer[0] = '\0';
273-
m_show_autocomplete = false;
274-
reclaim_focus = true;
275-
}
330+
ImGui::TableSetColumnIndex(2);
331+
ImGui::TextWrapped("%s", std::string(cvar->m_hint).c_str());
276332

277-
if (ImGui::IsItemActive())
278-
{
279-
if (m_show_autocomplete && !m_filtered_cvars.empty())
280-
{
281-
if (ImGui::IsKeyPressed(ImGuiKey_DownArrow))
282-
{
283-
m_autocomplete_selection = (m_autocomplete_selection + 1) % static_cast<int>(m_filtered_cvars.size());
284-
}
285-
else if (ImGui::IsKeyPressed(ImGuiKey_UpArrow))
286-
{
287-
m_autocomplete_selection = (m_autocomplete_selection - 1 + static_cast<int>(m_filtered_cvars.size())) % static_cast<int>(m_filtered_cvars.size());
288-
}
289-
else if (ImGui::IsKeyPressed(ImGuiKey_Tab))
290-
{
291-
ApplyAutocomplete();
292-
reclaim_focus = true;
333+
ImGui::PopID();
293334
}
335+
336+
ImGui::EndTable();
294337
}
295338
}
339+
ImGui::End();
296340

297-
if (ImGui::IsWindowAppearing() || reclaim_focus)
298-
{
299-
ImGui::SetKeyboardFocusHere(-1);
300-
}
341+
ImGui::PopStyleColor();
342+
ImGui::PopStyleVar(2);
301343
}
302344
}
303345

source/runtime/Core/Window.cpp

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ namespace spartan
6060
float titlebar_height = 40.0f; // default height, updated by editor
6161
float titlebar_button_width = 150.0f; // default width, updated by editor
6262
const float resize_border = 8.0f; // thickness of resize borders
63-
bool titlebar_has_hovered_item = false; // set by editor when imgui items are hovered
63+
int titlebar_hovered_frames = 0; // persistence counter for hover state
6464

6565
SDL_HitTestResult hit_test_callback(SDL_Window* win, const SDL_Point* area, void* data)
6666
{
@@ -95,8 +95,9 @@ namespace spartan
9595
// exclude window buttons area on the right
9696
if (x < w - static_cast<int>(titlebar_button_width))
9797
{
98-
// only allow dragging when no imgui items are hovered/active
99-
if (!titlebar_has_hovered_item)
98+
// only allow dragging when no imgui items were hovered recently
99+
// use persistence to avoid timing issues between hit test and imgui frame
100+
if (titlebar_hovered_frames == 0)
100101
{
101102
return SDL_HITTEST_DRAGGABLE;
102103
}
@@ -566,6 +567,16 @@ namespace spartan
566567

567568
void Window::SetTitleBarHovered(bool hovered)
568569
{
569-
titlebar_has_hovered_item = hovered;
570+
// use persistence to avoid timing issues between sdl hit test and imgui frame
571+
// when hovered, set counter high; when not hovered, decrement until zero
572+
const int persistence_frames = 3;
573+
if (hovered)
574+
{
575+
titlebar_hovered_frames = persistence_frames;
576+
}
577+
else if (titlebar_hovered_frames > 0)
578+
{
579+
titlebar_hovered_frames--;
580+
}
570581
}
571582
}

0 commit comments

Comments
 (0)