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

118 lines
4.6 KiB
Nim
Raw Normal View History

import strformat, strutils, times
import imguin/[cimgui, glfw_opengl, simple]
import ../utils/appImGui
import ../../common/[types]
type
ConsoleItem = ref object
timestamp: DateTime
logType: LogType
text: string
ConsoleItems = ref object
items: seq[string]
ConsoleComponent* = ref object of RootObj
agent: Agent
showConsole*: bool
inputBuffer: string
consoleItems: ConsoleItems
textSelect: ptr TextSelect
proc getNumLines(data: pointer): csize_t {.cdecl.} =
if data.isNil:
return 0
let consoleItems = cast[ConsoleItems](data)
return consoleItems.items.len().csize_t
proc getLineAtIndex(i: csize_t, data: pointer, outLen: ptr csize_t): cstring {.cdecl.} =
if data.isNil:
return nil
let consoleItems = cast[ConsoleItems](data)
let line = consoleItems.items[i].cstring
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
result.inputBuffer = ""
2025-09-11 19:11:11 +02:00
result.consoleItems = new ConsoleItems
result.consoleItems.items = @[]
result.textSelect = textselect_create(getLineAtIndex, getNumLines, cast[pointer](result.consoleItems), 0)
proc draw*(component: ConsoleComponent) =
igBegin(fmt"[{component.agent.agentId}] {component.agent.username}@{component.agent.hostname}", addr component.showConsole, 0)
defer: igEnd()
#[
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.
]#
let footerHeight = igGetStyle().ItemSpacing.y + igGetFrameHeightWithSpacing() * 2
if igBeginChild_Str("##Console", vec2(-1.0f, -footerHeight), ImGuiChildFlags_NavFlattened.int32, ImGuiWindowFlags_HorizontalScrollbar.int32):
2025-09-11 19:11:11 +02:00
# Display console items
for entry in component.consoleItems.items:
igTextColored(vec4(0.0f, 1.0f, 1.0f, 1.0f), entry.cstring)
2025-09-11 19:11:11 +02:00
component.textSelect.textselect_update()
# Auto-scroll to bottom if we're already at the bottom
if igGetScrollY() >= igGetScrollMaxY():
igSetScrollHereY(1.0f)
igEndChild()
# Buttons for testing the console
if igButton("Add Items", vec2(0.0f, 0.0f)):
for i in 1..10:
component.consoleItems.items.add("Hello world!")
igSameLine(0.0f, 5.0f)
if igButton("Add Long Items", vec2(0.0f, 0.0f)):
for i in 1..3:
component.consoleItems.items.add("Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet.")
igSameLine(0.0f, 5.0f)
if igButton("Clear", vec2(0.0f, 0.0f)):
component.consoleItems.items.setLen(0)
#[
Input field with prompt indicator
]#
igText(fmt"[{component.agent.agentId}]")
let spacing = igGetStyle().ItemSpacing.x
igSameLine(0.0f, spacing)
# Calculate available width for input
var availableWidth: ImVec2
igGetContentRegionAvail(addr availableWidth)
igSetNextItemWidth(availableWidth.x)
let inputFlags = ImGuiInputTextFlags_EnterReturnsTrue.int32 or ImGuiInputTextFlags_EscapeClearsAll.int32 or ImGuiInputTextFlags_CallbackCompletion.int32 or ImGuiInputTextFlags_CallbackHistory.int32
if igInputText("##Input", component.inputBuffer, 256, inputFlags, nil, nil):
discard
#[
Session information (optional footer)
]#
# igSeparator()
# let sessionInfo = fmt"{component.agent.username}@{component.agent.hostname} [{component.agent.ip}]"
# igText(sessionInfo)
igSetItemDefaultFocus()