CogEngine: console window improvements

This commit is contained in:
Arnaud Jamin
2025-01-31 23:33:22 -05:00
parent 2ede6b4cf9
commit 0429a7b313
3 changed files with 179 additions and 130 deletions
@@ -25,6 +25,7 @@ void FCogEngineWindow_Console::Initialize()
bNoPadding = true; bNoPadding = true;
bHasMenu = true; bHasMenu = true;
bHasWidget = 1; bHasWidget = 1;
SelectedCommandIndex = -1;
RefreshCommandList(); RefreshCommandList();
} }
@@ -95,28 +96,14 @@ void FCogEngineWindow_Console::RenderContent()
RenderConsoleTextInput(); RenderConsoleTextInput();
} }
if (ImGui::BeginChild("Commands", ImVec2(0.0f, -1.0f), ImGuiChildFlags_NavFlattened)) const ImVec2 Size = IsWindowRenderedInMainMenu() ? ImVec2(0, ImGui::GetFontSize() * 20) : ImVec2(0.0f, -1.0f);
if (ImGui::BeginChild("Commands", Size, ImGuiChildFlags_NavFlattened))
{ {
const float Indent = ImGui::GetFontSize() * 0.5f; const float Indent = ImGui::GetFontSize() * 0.5f;
ImGui::Indent(Indent); ImGui::Indent(Indent);
RenderCommandList(); RenderCommandList();
if (ImGui::IsWindowFocused(ImGuiFocusedFlags_RootAndChildWindows))
{
if (bIsWindowFocused == false)
{
bRequestTextInputFocus = true;
}
bIsWindowFocused = true;
HandleInputs();
}
else
{
bIsWindowFocused = false;
}
ImGui::Unindent(Indent); ImGui::Unindent(Indent);
} }
@@ -146,7 +133,9 @@ void FCogEngineWindow_Console::RenderConsoleTextInput()
ImGuiInputTextFlags_EnterReturnsTrue ImGuiInputTextFlags_EnterReturnsTrue
| ImGuiInputTextFlags_EscapeClearsAll | ImGuiInputTextFlags_EscapeClearsAll
| ImGuiInputTextFlags_CallbackCompletion | ImGuiInputTextFlags_CallbackCompletion
| ImGuiInputTextFlags_CallbackEdit; | ImGuiInputTextFlags_CallbackHistory
| ImGuiInputTextFlags_CallbackEdit
| ImGuiInputTextFlags_CallbackAlways;
if (FCogWindowWidgets::InputTextWithHint("##Command", "Command", CurrentUserInput, InputFlags, &OnTextInputCallbackStub, this)) if (FCogWindowWidgets::InputTextWithHint("##Command", "Command", CurrentUserInput, InputFlags, &OnTextInputCallbackStub, this))
{ {
@@ -156,44 +145,107 @@ void FCogEngineWindow_Console::RenderConsoleTextInput()
ImGui::SetItemDefaultFocus(); ImGui::SetItemDefaultFocus();
//-------------------------------------------------------------------------------------------------
// In Widget mode, do not want to show the command list as soon as the input text has focus,
// but wait for the user to click on the input text (or interact with it). This is because
// we want the text input to always have focus so the user can directly type text if he wants to,
// but if he doesn't the command list should not clutter the screen.
//-------------------------------------------------------------------------------------------------
if (bIsRenderingWidget && ImGui::IsItemActive() && ImGui::IsMouseClicked(ImGuiMouseButton_Left))
{
bPopupCommandListOnWidgetMode = true;
}
if (ImGui::IsItemActive() == false if (ImGui::IsItemActive() == false
&& bRequestTextInputFocus && bRequestTextInputFocus
&& ImGui::IsWindowFocused(ImGuiFocusedFlags_RootAndChildWindows) /*&& ImGui::IsWindowFocused(ImGuiFocusedFlags_RootAndChildWindows)*/
&& IsWindowRenderedInMainMenu() == false) /*&& IsWindowRenderedInMainMenu() == false*/)
{ {
ImGui::SetKeyboardFocusHere(-1); ImGui::SetKeyboardFocusHere(-1);
//bRequestTextInputFocus = false;
} }
if (ImGui::IsItemActive()) if (ImGui::IsItemActive())
{ {
bRequestTextInputFocus = false; bRequestTextInputFocus = false;
} }
} }
//-------------------------------------------------------------------------------------------------------------------------- //--------------------------------------------------------------------------------------------------------------------------
void FCogEngineWindow_Console::HandleInputs() int FCogEngineWindow_Console::OnTextInputCallback(ImGuiInputTextCallbackData* InData)
{ {
if (ImGui::IsKeyPressed(ImGuiKey_DownArrow)) bool DoCompletion = false;
if (InData->EventFlag == ImGuiInputTextFlags_CallbackHistory)
{ {
SelectedCommandIndex += 1; DoCompletion = true;
bScrollDirection = -1;
if (SelectedCommandIndex >= CommandList.Num()) if (InData->EventKey == ImGuiKey_UpArrow)
{ {
SelectedCommandIndex = 0; SelectedCommandIndex -= 1;
bScroll = true;
if (SelectedCommandIndex < 0)
{
SelectedCommandIndex = CommandList.Num() - 1;
}
}
else if (InData->EventKey == ImGuiKey_DownArrow)
{
SelectedCommandIndex += 1;
bScroll = true;
if (SelectedCommandIndex >= CommandList.Num())
{
SelectedCommandIndex = 0;
}
} }
} }
else if (ImGui::IsKeyPressed(ImGuiKey_UpArrow)) else if (InData->EventFlag == ImGuiInputTextFlags_CallbackCompletion)
{ {
SelectedCommandIndex -= 1; DoCompletion = true;
bScrollDirection = 1; }
else if (InData->EventFlag == ImGuiInputTextFlags_CallbackEdit)
{
CurrentUserInput = FString(InData->Buf);
RefreshCommandList();
if (SelectedCommandIndex < 0) if (bIsRenderingWidget)
{ {
SelectedCommandIndex = CommandList.Num() - 1; bPopupCommandListOnWidgetMode = true;
//ImGui::SetScrollHereY(1.0f);
} }
} }
else if (InData->EventFlag == ImGuiInputTextFlags_CallbackAlways)
{
if (bSetBufferToSelectedCommand)
{
DoCompletion = true;
bSetBufferToSelectedCommand = false;
}
}
if (DoCompletion && CommandList.IsValidIndex(SelectedCommandIndex))
{
const FString SelectedCommand = CommandList[SelectedCommandIndex];
const FString CleanupSelectedCommand = SelectedCommand.TrimEnd();
const auto& CommandStr = StringCast<ANSICHAR>(*CleanupSelectedCommand);
InData->DeleteChars(0, InData->BufTextLen);
InData->InsertChars(0, CommandStr.Get());
InData->InsertChars(InData->CursorPos, " ");
if (bIsRenderingWidget)
{
bPopupCommandListOnWidgetMode = true;
}
}
return 0;
}
//--------------------------------------------------------------------------------------------------------------------------
int FCogEngineWindow_Console::OnTextInputCallbackStub(ImGuiInputTextCallbackData* InData)
{
FCogEngineWindow_Console& ConsoleWindow = *static_cast<FCogEngineWindow_Console*>(InData->UserData);
return ConsoleWindow.OnTextInputCallback(InData);
} }
//-------------------------------------------------------------------------------------------------------------------------- //--------------------------------------------------------------------------------------------------------------------------
@@ -214,13 +266,7 @@ void FCogEngineWindow_Console::RenderCommandList()
{ {
ImGui::PushID(Index); ImGui::PushID(Index);
const FString& CommandName = CommandList[Index]; const FString& CommandName = CommandList[Index];
RenderCommand(CommandName, Index); RenderCommand(CommandName, Index);
if (SelectedCommandIndex == Index)
{
SelectedCommand = CommandName;
}
ImGui::PopID(); ImGui::PopID();
} }
} }
@@ -273,10 +319,16 @@ void FCogEngineWindow_Console::RenderCommand(const FString& CommandName, const i
ImGui::PushStyleColor(ImGuiCol_Text, FCogImguiHelper::ToImVec4(Config->HistoryColor)); ImGui::PushStyleColor(ImGuiCol_Text, FCogImguiHelper::ToImVec4(Config->HistoryColor));
} }
if (ImGui::Selectable(CommandNameStr.Get(), &IsSelected, ImGuiSelectableFlags_AllowDoubleClick)) ImGuiSelectableFlags Flags =
ImGuiSelectableFlags_AllowDoubleClick // Double click executes the selected command
| ImGuiSelectableFlags_SelectOnClick; // Need to focus the console text input right away, otherwise the Selectable take back the focus on mouse release
if (ImGui::Selectable(CommandNameStr.Get(), &IsSelected, Flags))
{ {
SelectedCommandIndex = Index; SelectedCommandIndex = Index;
CurrentUserInput = CommandName; bRequestTextInputFocus = true;
bSetBufferToSelectedCommand = true;
if (ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left)) if (ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left))
{ {
ExecuteCommand(CommandName); ExecuteCommand(CommandName);
@@ -288,13 +340,17 @@ void FCogEngineWindow_Console::RenderCommand(const FString& CommandName, const i
ImGui::PopStyleColor(); ImGui::PopStyleColor();
} }
if (NumHistoryCommands > 0 && Index == NumHistoryCommands - 1)
{
ImGui::Separator();
}
if (IsSelected) if (IsSelected)
{ {
SelectedCommand = CommandName; if (bScroll)
if (bScrollDirection != 0)
{ {
ImGui::SetScrollHereY(1.0f); ImGui::SetScrollHereY(1.0f);
bScrollDirection = 0; bScroll = false;
} }
} }
@@ -369,36 +425,10 @@ void FCogEngineWindow_Console::RefreshCommandList()
CommandList.Append(Commands); CommandList.Append(Commands);
SelectedCommandIndex = 0; //-------------------------------------------------------------------------------------
} // Reset to -1 so the next down arrow will select the first entry in history/command
//-------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------------------------------- SelectedCommandIndex = -1;
int FCogEngineWindow_Console::OnTextInputCallback(ImGuiInputTextCallbackData* InData)
{
if (InData->EventFlag == ImGuiInputTextFlags_CallbackCompletion)
{
FString CleanupCommand = SelectedCommand.TrimEnd();
const auto& CommandStr = StringCast<ANSICHAR>(*CleanupCommand);
InData->DeleteChars(0, InData->BufTextLen);
InData->InsertChars(0, CommandStr.Get());
InData->InsertChars(InData->CursorPos, " ");
TextInputState = ImGuiInputTextFlags_CallbackCompletion;
}
else if (InData->EventFlag == ImGuiInputTextFlags_CallbackEdit)
{
CurrentUserInput = FString(InData->Buf);
RefreshCommandList();
TextInputState = ImGuiInputTextFlags_CallbackCompletion;
}
return 0;
}
//--------------------------------------------------------------------------------------------------------------------------
int FCogEngineWindow_Console::OnTextInputCallbackStub(ImGuiInputTextCallbackData* InData)
{
FCogEngineWindow_Console& ConsoleWindow = *static_cast<FCogEngineWindow_Console*>(InData->UserData);
return ConsoleWindow.OnTextInputCallback(InData);
} }
//-------------------------------------------------------------------------------------------------------------------------- //--------------------------------------------------------------------------------------------------------------------------
@@ -415,6 +445,11 @@ void FCogEngineWindow_Console::ExecuteCommand(const FString& InCommand)
GEngine->DeferredCommands.Add(CleanupCommand); GEngine->DeferredCommands.Add(CleanupCommand);
} }
if (bIsRenderingWidget)
{
bPopupCommandListOnWidgetMode = false;
}
CurrentUserInput = FString(); CurrentUserInput = FString();
RefreshCommandList(); RefreshCommandList();
} }
@@ -422,52 +457,50 @@ void FCogEngineWindow_Console::ExecuteCommand(const FString& InCommand)
//-------------------------------------------------------------------------------------------------------------------------- //--------------------------------------------------------------------------------------------------------------------------
void FCogEngineWindow_Console::RenderMainMenuWidget() void FCogEngineWindow_Console::RenderMainMenuWidget()
{ {
bIsRenderingWidget = true;
const ImGuiContext& g = *GImGui; const ImGuiContext& g = *GImGui;
const ImGuiStyle& Style = g.Style; const ImGuiStyle& Style = g.Style;
const ImGuiWindow* Window = ImGui::GetCurrentWindow(); const ImGuiWindow* Window = ImGui::GetCurrentWindow();
ImVec2 TooltipPos = Window->DC.CursorPos; ImVec2 TooltipPos = Window->DC.CursorPos;
TooltipPos.y += Window->MenuBarHeight; TooltipPos.y += Window->MenuBarHeight;
const float Width = ImGui::GetFontSize() * 20; ImGui::SetNextItemWidth(-1);
ImGui::SetNextItemWidth(Width);
RenderConsoleTextInput(); RenderConsoleTextInput();
const float Width = ImGui::GetItemRectSize().x;
const bool IsTextInputActive = ImGui::IsItemActive(); if (ImGui::IsWindowAppearing())
if (ImGui::IsItemActivated())
{ {
bShowCommandsInWidget = true; bRequestTextInputFocus = true;
} }
if (bShowCommandsInWidget) const bool IsTextInputActive = ImGui::IsItemActive();
if (bPopupCommandListOnWidgetMode)
{ {
ImGui::SetNextWindowBgAlpha(g.Style.Colors[ImGuiCol_PopupBg].w * 0.60f); ImGui::SetNextWindowBgAlpha(g.Style.Colors[ImGuiCol_PopupBg].w * 0.90f);
ImGui::SetNextWindowSize(ImVec2(Width, ImGui::GetFontSize() * 30), ImGuiCond_Always); ImGui::SetNextWindowSize(ImVec2(Width, ImGui::GetFontSize() * 30), ImGuiCond_FirstUseEver);
ImGui::SetNextWindowPos(TooltipPos, ImGuiCond_Always); ImGui::SetNextWindowPos(TooltipPos, ImGuiCond_Always);
ImGuiWindowFlags flags = ImGuiWindowFlags flags =
ImGuiWindowFlags_NoTitleBar ImGuiWindowFlags_NoTitleBar
//| ImGuiWindowFlags_NoNav | ImGuiWindowFlags_NoMove
| ImGuiWindowFlags_NoMove; | ImGuiWindowFlags_NoFocusOnAppearing; // We want the console input text to keep the focus.
//| ImGuiWindowFlags_NoBringToFrontOnFocus
//| ImGuiWindowFlags_NoFocusOnAppearing;
if (ImGui::Begin("ConsoleTooltip", 0, flags)) if (ImGui::Begin("ConsoleTooltip", nullptr, flags))
{ {
RenderCommandList(); RenderCommandList();
HandleInputs();
const bool IsWindowFocused = ImGui::IsWindowFocused(ImGuiFocusedFlags_RootAndChildWindows); const bool IsWindowFocused = ImGui::IsWindowFocused(ImGuiFocusedFlags_RootWindow | ImGuiFocusedFlags_NoPopupHierarchy);
if (IsWindowFocused)
{
bRequestTextInputFocus = true;
}
if (IsTextInputActive == false && IsWindowFocused == false) if (IsTextInputActive == false && IsWindowFocused == false && bRequestTextInputFocus == false)
{ {
bShowCommandsInWidget = false; bPopupCommandListOnWidgetMode = false;
} }
} }
ImGui::End(); ImGui::End();
} }
bIsRenderingWidget = false;
} }
@@ -31,7 +31,6 @@ private:
static FString GetConsoleCommandHelp(const FString& InCommandName); static FString GetConsoleCommandHelp(const FString& InCommandName);
void RenderConsoleTextInput(); void RenderConsoleTextInput();
void HandleInputs();
void RenderCommand(const FString& CommandName, int32 Index); void RenderCommand(const FString& CommandName, int32 Index);
@@ -41,9 +40,7 @@ private:
void ExecuteCommand(const FString& InCommand); void ExecuteCommand(const FString& InCommand);
int32 SelectedCommandIndex = 0; int32 SelectedCommandIndex = -1;
FString SelectedCommand;
TArray<FString> CommandList; TArray<FString> CommandList;
@@ -51,17 +48,19 @@ private:
FString CurrentUserInput; FString CurrentUserInput;
int32 bScrollDirection = 0; bool bScroll = false;
bool bRequestTextInputFocus = false; bool bRequestTextInputFocus = false;
bool bIsWindowFocused = false; bool bIsWindowFocused = false;
bool bShowCommandsInWidget = false; bool bPopupCommandListOnWidgetMode = false;
bool bIsRenderingWidget = false;
bool bSetBufferToSelectedCommand = false;
TWeakObjectPtr<UCogEngineConfig_Console> Config; TWeakObjectPtr<UCogEngineConfig_Console> Config;
ImGuiInputTextFlags TextInputState;
}; };
//-------------------------------------------------------------------------------------------------------------------------- //--------------------------------------------------------------------------------------------------------------------------
@@ -372,7 +372,16 @@ void UCogWindowManager::RenderMainMenu()
IsRenderingInMainMenu = true; IsRenderingInMainMenu = true;
if (ImGui::BeginMainMenuBar()) //-----------------------------------------------------------------------------------------------
// Prevent having a small gap on the right of the main menu, where some widgets are displayed
//-----------------------------------------------------------------------------------------------
ImGui::PushStyleVarX(ImGuiStyleVar_WindowPadding, 0.0f);
const bool ShowMainMenu = ImGui::BeginMainMenuBar();
ImGui::PopStyleVar();
if (ShowMainMenu)
{ {
for (FMenu& Menu : MainMenu.SubMenus) for (FMenu& Menu : MainMenu.SubMenus)
{ {
@@ -408,7 +417,6 @@ void UCogWindowManager::RenderMainMenu()
} }
RenderWidgets(); RenderWidgets();
ImGui::EndMainMenuBar(); ImGui::EndMainMenuBar();
} }
@@ -418,30 +426,35 @@ void UCogWindowManager::RenderMainMenu()
//-------------------------------------------------------------------------------------------------------------------------- //--------------------------------------------------------------------------------------------------------------------------
void UCogWindowManager::RenderWidgets() void UCogWindowManager::RenderWidgets()
{ {
int32 numVisibleWidgets = 0; int32 NumVisibleWidgets = 0;
for (int i = 0; i < Widgets.Num(); ++i) for (int i = 0; i < Widgets.Num(); ++i)
{ {
FCogWindow* Window = Widgets[i]; FCogWindow* Window = Widgets[i];
if (Window->GetIsWidgetVisible()) if (Window->GetIsWidgetVisible())
{ {
numVisibleWidgets++; NumVisibleWidgets++;
} }
} }
if (numVisibleWidgets == 0) if (NumVisibleWidgets == 0)
{ return; } { return; }
int32 numColumns = numVisibleWidgets; const bool AddLeftColumn = Settings->WidgetAlignment == ECogWidgetAlignment::Right
|| Settings->WidgetAlignment == ECogWidgetAlignment::Manual
|| Settings->WidgetAlignment == ECogWidgetAlignment::Center;
if (Settings->WidgetAlignment == ECogWidgetAlignment::Right) const bool AddRightColumn = Settings->WidgetAlignment == ECogWidgetAlignment::Center;
int32 NumColumns = NumVisibleWidgets;
if (AddLeftColumn)
{ {
numColumns++; NumColumns++;
}
else if (Settings->WidgetAlignment == ECogWidgetAlignment::Center)
{
numColumns += 2;
} }
if (AddRightColumn)
{
NumColumns ++;
}
ImGuiTableFlags Flags = ImGuiTableFlags_None; ImGuiTableFlags Flags = ImGuiTableFlags_None;
if (Settings->ShowWidgetBorders) if (Settings->ShowWidgetBorders)
@@ -462,26 +475,28 @@ void UCogWindowManager::RenderWidgets()
} }
} }
if (ImGui::BeginTable("Widgets", numColumns, Flags)) ImGui::PushStyleVarX(ImGuiStyleVar_CellPadding, 0.0f);
if (ImGui::BeginTable("Widgets", NumColumns, Flags))
{ {
if (Settings->WidgetAlignment == ECogWidgetAlignment::Right || Settings->WidgetAlignment == ECogWidgetAlignment::Center) if (AddLeftColumn)
{ {
ImGui::TableSetupColumn("Stretch", ImGuiTableColumnFlags_WidthStretch); ImGui::TableSetupColumn("Stretch", ImGuiTableColumnFlags_WidthStretch);
} }
for (int i = 0; i < numVisibleWidgets; ++i) for (int i = 0; i < NumVisibleWidgets; ++i)
{ {
ImGui::TableSetupColumn("Fixed", ImGuiTableColumnFlags_WidthFixed); ImGui::TableSetupColumn("Fixed", ImGuiTableColumnFlags_WidthFixed);
} }
if (Settings->WidgetAlignment == ECogWidgetAlignment::Center) if (AddRightColumn)
{ {
ImGui::TableSetupColumn("Stretch", ImGuiTableColumnFlags_WidthStretch); ImGui::TableSetupColumn("Stretch", ImGuiTableColumnFlags_WidthStretch);
} }
ImGui::TableNextRow(); ImGui::TableNextRow();
if (Settings->WidgetAlignment == ECogWidgetAlignment::Right || Settings->WidgetAlignment == ECogWidgetAlignment::Center) if (AddLeftColumn)
{ {
ImGui::TableNextColumn(); ImGui::TableNextColumn();
} }
@@ -498,13 +513,15 @@ void UCogWindowManager::RenderWidgets()
Window->RenderMainMenuWidget(); Window->RenderMainMenuWidget();
} }
if (Settings->WidgetAlignment == ECogWidgetAlignment::Center) if (AddRightColumn)
{ {
ImGui::TableNextColumn(); ImGui::TableNextColumn();
} }
ImGui::EndTable(); ImGui::EndTable();
} }
ImGui::PopStyleVar();
} }
//-------------------------------------------------------------------------------------------------------------------------- //--------------------------------------------------------------------------------------------------------------------------