Files
conquest/src/client/views/console.nim

142 lines
5.3 KiB
Nim
Raw Normal View History

import strformat, strutils, times
import imguin/[cimgui, glfw_opengl, simple]
import ../utils/appImGui
import ../../common/[types]
2025-09-16 20:17:48 +02:00
const MAX_INPUT_LENGTH = 512
type
ConsoleComponent* = ref object of RootObj
agent: Agent
showConsole*: bool
2025-09-16 20:17:48 +02:00
inputBuffer: array[MAX_INPUT_LENGTH, char]
console: ConsoleItems
textSelect: ptr TextSelect
2025-09-16 20:17:48 +02:00
proc getItemText(item: ConsoleItem): cstring =
let timestamp = item.timestamp.format("dd-MM-yyyy HH:mm:ss")
return fmt"[{timestamp}] {$item.itemType} {item.text}".string
proc getNumLines(data: pointer): csize_t {.cdecl.} =
if data.isNil:
return 0
2025-09-16 20:17:48 +02:00
let console = cast[ConsoleItems](data)
return console.items.len().csize_t
proc getLineAtIndex(i: csize_t, data: pointer, outLen: ptr csize_t): cstring {.cdecl.} =
if data.isNil:
return nil
2025-09-16 20:17:48 +02:00
let console = cast[ConsoleItems](data)
let line = getItemText(console.items[i])
if not outLen.isNil:
outLen[] = line.len.csize_t
return line
proc Console*(agent: Agent): ConsoleComponent =
result = new ConsoleComponent
result.agent = agent
result.showConsole = true
2025-09-16 20:17:48 +02:00
zeroMem(addr result.inputBuffer[0], MAX_INPUT_LENGTH)
2025-09-11 19:11:11 +02:00
2025-09-16 20:17:48 +02:00
result.console = new ConsoleItems
result.console.items = @[]
result.textSelect = textselect_create(getLineAtIndex, getNumLines, cast[pointer](result.console), 0)
proc draw*(component: ConsoleComponent) =
igBegin(fmt"[{component.agent.agentId}] {component.agent.username}@{component.agent.hostname}", addr component.showConsole, 0)
defer: igEnd()
2025-09-16 20:17:48 +02:00
var focusInput = false
#[
Console items/text section using ImGuiTextSelect in a child window
Supports:
- horizontal+vertical scrolling,
- autoscroll
- colored text
- text selection and copy functionality
Problems I encountered with other approaches (Multi-line Text Input, TextEditor, ...):
- https://github.com/ocornut/imgui/issues/383#issuecomment-2080346129
- https://github.com/ocornut/imgui/issues/950
Huge thanks to @dinau for implementing ImGuiTextSelect into imguin very rapidly after I requested it.
]#
2025-09-16 20:17:48 +02:00
let consolePadding: float = 10.0f
let footerHeight = (consolePadding * 2) + (igGetStyle().ItemSpacing.y + igGetFrameHeightWithSpacing())
let textSpacing = igGetStyle().ItemSpacing.x
# Padding
igDummy(vec2(0.0f, consolePadding))
try:
# Set styles of the console window
igPushStyleColor_Vec4(ImGui_Col_FrameBg.int32, vec4(0.1f, 0.1f, 0.1f, 1.0f))
igPushStyleColor_Vec4(ImGui_Col_ScrollbarBg.int32, vec4(0.1f, 0.1f, 0.1f, 1.0f))
igPushStyleColor_Vec4(ImGui_Col_Border.int32, vec4(0.2f, 0.2f, 0.2f, 1.0f))
igPushStyleVar_Float(ImGui_StyleVar_FrameBorderSize .int32, 1.0f)
let childWindowFlags = ImGuiChildFlags_NavFlattened.int32 or ImGui_ChildFlags_Borders.int32 or ImGui_ChildFlags_AlwaysUseWindowPadding.int32 or ImGuiChildFlags_FrameStyle.int32
if igBeginChild_Str("##Console", vec2(-1.0f, -footerHeight), childWindowFlags, ImGuiWindowFlags_HorizontalScrollbar.int32):
# Display console items
for entry in component.console.items:
let timestamp = entry.timestamp.format("dd-MM-yyyy HH:mm:ss")
igTextColored(vec4(0.6f, 0.6f, 0.6f, 1.0f), fmt"[{timestamp}]".cstring)
igSameLine(0.0f, textSpacing)
igTextColored(vec4(0.0f, 1.0f, 1.0f, 1.0f), $entry.itemType)
igSameLine(0.0f, textSpacing)
igTextUnformatted(entry.text.cstring, nil)
component.textSelect.textselect_update()
# Auto-scroll to bottom
if igGetScrollY() >= igGetScrollMaxY():
igSetScrollHereY(1.0f)
except IndexDefect:
# CTRL+A crashes when no items are in the console
discard
2025-09-16 20:17:48 +02:00
finally:
igPopStyleColor(3)
igPopStyleVar(1)
igEndChild()
2025-09-16 20:17:48 +02:00
# Padding
igDummy(vec2(0.0f, consolePadding))
#[
Input field with prompt indicator
]#
igText(fmt"[{component.agent.agentId}]")
2025-09-16 20:17:48 +02:00
igSameLine(0.0f, textSpacing)
# Calculate available width for input
var availableWidth: ImVec2
igGetContentRegionAvail(addr availableWidth)
igSetNextItemWidth(availableWidth.x)
2025-09-16 20:17:48 +02:00
let inputFlags = ImGuiInputTextFlags_EnterReturnsTrue.int32 or ImGuiInputTextFlags_EscapeClearsAll.int32 # or ImGuiInputTextFlags_CallbackCompletion.int32 or ImGuiInputTextFlags_CallbackHistory.int32
if igInputText("##Input", addr component.inputBuffer[0], MAX_INPUT_LENGTH, inputFlags, nil, nil):
let command = $(addr component.inputBuffer[0]).cstring
let commandItem = ConsoleItem(
timestamp: now(),
itemType: LOG_COMMAND,
text: command
)
component.console.items.add(commandItem)
# TODO: Handle command execution
zeroMem(addr component.inputBuffer[0], MAX_INPUT_LENGTH)
focusInput = true
#[
Session information (optional footer)
]#
# igSeparator()
# let sessionInfo = fmt"{component.agent.username}@{component.agent.hostname} [{component.agent.ip}]"
# igText(sessionInfo)
igSetItemDefaultFocus()
2025-09-16 20:17:48 +02:00
if focusInput:
igSetKeyboardFocusHere(-1)