Implemented console items window using ImGuiTextSelect after it was implemented into imguin.
This commit is contained in:
@@ -28,5 +28,5 @@ requires "tiny_sqlite >= 0.2.0"
|
|||||||
requires "prologue >= 0.6.6"
|
requires "prologue >= 0.6.6"
|
||||||
requires "winim >= 3.9.4"
|
requires "winim >= 3.9.4"
|
||||||
requires "ptr_math >= 0.3.0"
|
requires "ptr_math >= 0.3.0"
|
||||||
requires "imguin >= 1.92.2.0"
|
requires "imguin >= 1.92.2.1"
|
||||||
requires "zippy >= 0.10.16"
|
requires "zippy >= 0.10.16"
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
switch "o", "bin/client"
|
switch "o", "bin/client"
|
||||||
|
|
||||||
switch "d", "ImColorTextEdit"
|
switch "d", "ImGuiTextSelect"
|
||||||
|
|
||||||
# Select compiler
|
# Select compiler
|
||||||
var TC = "gcc"
|
var TC = "gcc"
|
||||||
|
|||||||
@@ -1,41 +1,41 @@
|
|||||||
[Window][Sessions [Table View]]
|
[Window][Sessions [Table View]]
|
||||||
Pos=10,43
|
Pos=10,43
|
||||||
Size=1477,421
|
Size=2117,381
|
||||||
Collapsed=0
|
Collapsed=0
|
||||||
DockId=0x00000003,0
|
DockId=0x00000003,0
|
||||||
|
|
||||||
[Window][Listeners]
|
[Window][Listeners]
|
||||||
Pos=10,466
|
Pos=10,426
|
||||||
Size=1888,523
|
Size=2528,971
|
||||||
Collapsed=0
|
Collapsed=0
|
||||||
DockId=0x00000002,0
|
DockId=0x00000002,0
|
||||||
|
|
||||||
[Window][Eventlog]
|
[Window][Eventlog]
|
||||||
Pos=1489,43
|
Pos=2129,43
|
||||||
Size=409,421
|
Size=409,381
|
||||||
Collapsed=0
|
Collapsed=0
|
||||||
DockId=0x00000004,0
|
DockId=0x00000004,0
|
||||||
|
|
||||||
[Window][Dear ImGui Demo]
|
[Window][Dear ImGui Demo]
|
||||||
Pos=1489,43
|
Pos=2129,43
|
||||||
Size=409,421
|
Size=409,381
|
||||||
Collapsed=0
|
Collapsed=0
|
||||||
DockId=0x00000004,1
|
DockId=0x00000004,1
|
||||||
|
|
||||||
[Window][Dockspace]
|
[Window][Dockspace]
|
||||||
Pos=0,0
|
Pos=0,0
|
||||||
Size=1908,999
|
Size=2548,1407
|
||||||
Collapsed=0
|
Collapsed=0
|
||||||
|
|
||||||
[Window][[FACEDEAD] bob@LAPTOP-02]
|
[Window][[FACEDEAD] bob@LAPTOP-02]
|
||||||
Pos=10,466
|
Pos=10,426
|
||||||
Size=1888,523
|
Size=2528,971
|
||||||
Collapsed=0
|
Collapsed=0
|
||||||
DockId=0x00000002,3
|
DockId=0x00000002,3
|
||||||
|
|
||||||
[Window][[C9D8E7F6] charlie@SERVER-03]
|
[Window][[C9D8E7F6] charlie@SERVER-03]
|
||||||
Pos=10,466
|
Pos=10,426
|
||||||
Size=1888,523
|
Size=2528,971
|
||||||
Collapsed=0
|
Collapsed=0
|
||||||
DockId=0x00000002,2
|
DockId=0x00000002,2
|
||||||
|
|
||||||
@@ -45,8 +45,8 @@ Size=400,400
|
|||||||
Collapsed=0
|
Collapsed=0
|
||||||
|
|
||||||
[Window][[G1H2I3J5] diana@WORKSTATION-04]
|
[Window][[G1H2I3J5] diana@WORKSTATION-04]
|
||||||
Pos=10,466
|
Pos=10,426
|
||||||
Size=1888,523
|
Size=2528,971
|
||||||
Collapsed=0
|
Collapsed=0
|
||||||
DockId=0x00000002,1
|
DockId=0x00000002,1
|
||||||
|
|
||||||
@@ -78,23 +78,23 @@ Size=1717,576
|
|||||||
Collapsed=0
|
Collapsed=0
|
||||||
|
|
||||||
[Table][0x32886A44,8]
|
[Table][0x32886A44,8]
|
||||||
Column 0 Weight=0.6522
|
Column 0 Weight=0.6513
|
||||||
Column 1 Weight=0.9755
|
Column 1 Weight=0.9753
|
||||||
Column 2 Weight=0.5541
|
Column 2 Weight=0.5524
|
||||||
Column 3 Weight=1.0620
|
Column 3 Weight=1.0605
|
||||||
Column 4 Weight=1.7316
|
Column 4 Weight=1.7323
|
||||||
Column 5 Weight=1.1486
|
Column 5 Weight=1.1492
|
||||||
Column 6 Weight=0.3290
|
Column 6 Weight=0.4263
|
||||||
Column 7 Weight=1.5469
|
Column 7 Weight=1.4527
|
||||||
|
|
||||||
[Table][0xB6880529,2]
|
[Table][0xB6880529,2]
|
||||||
RefScale=27
|
RefScale=27
|
||||||
Column 0 Sort=0v
|
Column 0 Sort=0v
|
||||||
|
|
||||||
[Docking][Data]
|
[Docking][Data]
|
||||||
DockSpace ID=0x85940918 Window=0x260A4489 Pos=10,43 Size=1888,946 Split=Y
|
DockSpace ID=0x85940918 Window=0x260A4489 Pos=10,43 Size=2528,1354 Split=Y
|
||||||
DockNode ID=0x00000001 Parent=0x85940918 SizeRef=1024,421 Split=X
|
DockNode ID=0x00000001 Parent=0x85940918 SizeRef=1024,381 Split=X
|
||||||
DockNode ID=0x00000003 Parent=0x00000001 SizeRef=613,159 CentralNode=1 Selected=0x61E02D75
|
DockNode ID=0x00000003 Parent=0x00000001 SizeRef=613,159 CentralNode=1 Selected=0x61E02D75
|
||||||
DockNode ID=0x00000004 Parent=0x00000001 SizeRef=409,159 Selected=0x5E5F7166
|
DockNode ID=0x00000004 Parent=0x00000001 SizeRef=409,159 Selected=0x5E5F7166
|
||||||
DockNode ID=0x00000002 Parent=0x85940918 SizeRef=1024,523 Selected=0x8D780333
|
DockNode ID=0x00000002 Parent=0x85940918 SizeRef=1024,971 Selected=0x8D780333
|
||||||
|
|
||||||
|
|||||||
@@ -1,118 +1,117 @@
|
|||||||
import strformat, strutils
|
import strformat, strutils, times
|
||||||
import imguin/[cimgui, glfw_opengl, simple]
|
import imguin/[cimgui, glfw_opengl, simple]
|
||||||
import ../utils/appImGui
|
import ../utils/appImGui
|
||||||
import ../../common/[types]
|
import ../../common/[types]
|
||||||
|
|
||||||
type
|
type
|
||||||
|
ConsoleItem = ref object
|
||||||
|
timestamp: DateTime
|
||||||
|
logType: LogType
|
||||||
|
text: string
|
||||||
|
|
||||||
|
ConsoleItems = ref object
|
||||||
|
items: seq[string]
|
||||||
|
|
||||||
ConsoleComponent* = ref object of RootObj
|
ConsoleComponent* = ref object of RootObj
|
||||||
agent: Agent
|
agent: Agent
|
||||||
showConsole*: bool
|
showConsole*: bool
|
||||||
inputBuffer: string
|
inputBuffer: string
|
||||||
consoleEntries: seq[string]
|
consoleItems: ConsoleItems
|
||||||
console: ptr TextEditor
|
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 =
|
proc Console*(agent: Agent): ConsoleComponent =
|
||||||
result = new ConsoleComponent
|
result = new ConsoleComponent
|
||||||
result.agent = agent
|
result.agent = agent
|
||||||
result.showConsole = true
|
result.showConsole = true
|
||||||
result.console = TextEditor_TextEditor()
|
result.inputBuffer = ""
|
||||||
result.consoleEntries = @[
|
|
||||||
"a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a",
|
|
||||||
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
|
|
||||||
]
|
|
||||||
|
|
||||||
result.console.TextEditor_SetText(result.consoleEntries.join("\n") & '\0')
|
result.consoleItems = new ConsoleItems
|
||||||
|
result.consoleItems.items = @[]
|
||||||
proc findLongestLength(text: string): float32 =
|
result.textSelect = textselect_create(getLineAtIndex, getNumLines, cast[pointer](result.consoleItems), 0)
|
||||||
var maxWidth = 0.0f
|
|
||||||
for line in text.splitLines():
|
|
||||||
let line_cstring = line.cstring
|
|
||||||
var textSizeOut: ImVec2
|
|
||||||
igCalcTextSize(addr textSizeOut, line_cstring, nil, false, -1.0f)
|
|
||||||
if textSizeOut.x > maxWidth:
|
|
||||||
maxWidth = textSizeOut.x
|
|
||||||
return maxWidth
|
|
||||||
|
|
||||||
proc draw*(component: ConsoleComponent) =
|
proc draw*(component: ConsoleComponent) =
|
||||||
|
|
||||||
igBegin(fmt"[{component.agent.agentId}] {component.agent.username}@{component.agent.hostname}", addr component.showConsole, 0)
|
igBegin(fmt"[{component.agent.agentId}] {component.agent.username}@{component.agent.hostname}", addr component.showConsole, 0)
|
||||||
defer: igEnd()
|
defer: igEnd()
|
||||||
|
|
||||||
#[
|
#[
|
||||||
Console entries/text section
|
Console items/text section using ImGuiTextSelect in a child window
|
||||||
|
Supports:
|
||||||
|
- horizontal+vertical scrolling,
|
||||||
|
- autoscroll
|
||||||
|
- colored text
|
||||||
|
- text selection and copy functionality
|
||||||
|
|
||||||
Problems:
|
Problems I encountered with other approaches (Multi-line Text Input, TextEditor, ...):
|
||||||
# A InputTextMultiline component is placed within a Child Frame to enable both proper text selection and a horizontal scrollbar
|
- https://github.com/ocornut/imgui/issues/383#issuecomment-2080346129
|
||||||
# The only thing missing from this implementation is the ability change the text color and auto-scrolling
|
- https://github.com/ocornut/imgui/issues/950
|
||||||
# https://github.com/ocornut/imgui/issues/383#issuecomment-2080346129
|
Huge thanks to @dinau for implementing ImGuiTextSelect into imguin very rapidly after I requested it.
|
||||||
# https://github.com/ocornut/imgui/issues/950
|
|
||||||
]#
|
]#
|
||||||
let footerHeight = igGetStyle().ItemSpacing.y + igGetFrameHeightWithSpacing() # * 2
|
let footerHeight = igGetStyle().ItemSpacing.y + igGetFrameHeightWithSpacing() * 2
|
||||||
let buffer = component.consoleEntries.join("\n") & '\0'
|
|
||||||
|
|
||||||
# Push styles to hide the Child's background and scrollbar background.
|
if igBeginChild_Str("##Console", vec2(-1.0f, -footerHeight), ImGuiChildFlags_NavFlattened.int32, ImGuiWindowFlags_HorizontalScrollbar.int32):
|
||||||
igPushStyleColor_Vec4(ImGuiCol_FrameBg.int32, vec4(0.0f, 0.0f, 0.0f, 0.0f))
|
|
||||||
igPushStyleColor_Vec4(ImGuiCol_ScrollbarBg.int32, vec4(0.0f, 0.0f, 0.0f, 0.0f))
|
|
||||||
|
|
||||||
if igBeginChild_Str("##Console", vec2(-0.99f, -footerHeight), ImGuiChildFlags_NavFlattened.int32, ImGuiWindowFlags_HorizontalScrollbar.int32):
|
# Display console items
|
||||||
|
for entry in component.consoleItems.items:
|
||||||
|
igTextColored(vec4(0.0f, 1.0f, 1.0f, 1.0f), entry.cstring)
|
||||||
|
|
||||||
# Manually handle horizontal scrolling with the mouse wheel/touchpad
|
component.textSelect.textselect_update()
|
||||||
let io = igGetIO()
|
|
||||||
if io.MouseWheelH != 0:
|
|
||||||
let scroll_delta = io.MouseWheelH * igGetScrollX() * 0.5
|
|
||||||
igSetScrollX_Float(igGetScrollX() - scroll_delta)
|
|
||||||
if igGetScrollX() == 0:
|
|
||||||
igSetScrollX_Float(1.0f) # This is required to prevent the horizontal scrolling from snapping in
|
|
||||||
|
|
||||||
# Retrieve the length of the longes console entry
|
# Auto-scroll to bottom if we're already at the bottom
|
||||||
var width = findLongestLength(buffer)
|
if igGetScrollY() >= igGetScrollMaxY():
|
||||||
if width <= io.DisplaySize.x:
|
igSetScrollHereY(1.0f)
|
||||||
width = -1.0f
|
|
||||||
|
|
||||||
# Set the Text edit background color and make it visible.
|
|
||||||
igPushStyleColor_Vec4(ImGuiCol_FrameBg.int32, vec4(0.1f, 0.1f, 0.1f, 1.0f))
|
|
||||||
igPushStyleColor_Vec4(ImGuiCol_ScrollbarBg.int32, vec4(0.1f, 0.1f, 0.1f, 1.0f))
|
|
||||||
discard igInputTextMultiline("##ConsoleText", buffer, cast[csize_t](buffer.len()), vec2(width, -1.0f), ImGui_InputTextFlags_ReadOnly.int32 or ImGui_InputTextFlags_AllowTabInput.int32, nil, nil)
|
|
||||||
|
|
||||||
# Alternative: ImGuiColorTextEdit
|
|
||||||
# component.console.TextEditor_SetReadOnlyEnabled(true)
|
|
||||||
# component.console.TextEditor_SetShowLineNumbersEnabled(false)
|
|
||||||
# component.console.TextEditor_Render("##ConsoleEntries", false, vec2(-1, -1), true)
|
|
||||||
|
|
||||||
# # Scroll to bottom
|
|
||||||
# if igGetScrollY() >= igGetScrollMaxY():
|
|
||||||
# let lineCount = component.console.TextEditor_GetLineCount()
|
|
||||||
# component.console.TextEditor_SetCursorPosition(lineCount, 0)
|
|
||||||
|
|
||||||
igPopStyleColor(2)
|
|
||||||
|
|
||||||
igPopStyleColor(2)
|
|
||||||
igEndChild()
|
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
|
Input field with prompt indicator
|
||||||
]#
|
]#
|
||||||
let promptIndicator = fmt"[{component.agent.agentId}]"
|
igText(fmt"[{component.agent.agentId}]")
|
||||||
var charWidth: ImVec2
|
|
||||||
igCalcTextSize(addr charWidth, "A", nil, false, -1.0f)
|
|
||||||
let promptWidth = charWidth.x * float(promptIndicator.len())
|
|
||||||
let spacing = igGetStyle().ItemSpacing.x
|
let spacing = igGetStyle().ItemSpacing.x
|
||||||
|
|
||||||
igTextColored(vec4(1.0f, 1.0f, 1.0f, 1.0f), promptIndicator)
|
|
||||||
igSameLine(0.0f, spacing)
|
igSameLine(0.0f, spacing)
|
||||||
|
|
||||||
|
# Calculate available width for input
|
||||||
var availableWidth: ImVec2
|
var availableWidth: ImVec2
|
||||||
igGetContentRegionAvail(addr availableWidth)
|
igGetContentRegionAvail(addr availableWidth)
|
||||||
|
|
||||||
igSetNextItemWidth(availableWidth.x)
|
igSetNextItemWidth(availableWidth.x)
|
||||||
|
|
||||||
let inputFlags = ImGuiInputTextFlags_EnterReturnsTrue.int32 or ImGuiInputTextFlags_EscapeClearsAll.int32 or ImGuiInputTextFlags_CallbackCompletion.int32 or ImGuiInputTextFlags_CallbackHistory.int32
|
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):
|
if igInputText("##Input", component.inputBuffer, 256, inputFlags, nil, nil):
|
||||||
echo component.inputBuffer
|
discard
|
||||||
|
|
||||||
#[
|
#[
|
||||||
Session information (requires footerHeight to be doubled)
|
Session information (optional footer)
|
||||||
]#
|
]#
|
||||||
# igSeparator()
|
# igSeparator()
|
||||||
# igText(fmt"{component.agent.username}@{component.agent.hostname} [{component.agent.ip}]")
|
# let sessionInfo = fmt"{component.agent.username}@{component.agent.hostname} [{component.agent.ip}]"
|
||||||
|
# igText(sessionInfo)
|
||||||
|
|
||||||
igSetItemDefaultFocus()
|
igSetItemDefaultFocus()
|
||||||
|
|||||||
Reference in New Issue
Block a user