@@ -20,30 +20,187 @@ package com.lambda.gui.components
2020import com.lambda.core.Loadable
2121import com.lambda.event.events.GuiEvent
2222import com.lambda.event.listener.SafeListener.Companion.listen
23+ import com.lambda.gui.dsl.ImGuiBuilder
2324import com.lambda.gui.dsl.ImGuiBuilder.buildLayout
25+ import com.lambda.gui.snap.Guide
26+ import com.lambda.gui.snap.RectF
27+ import com.lambda.gui.snap.SnapManager
2428import com.lambda.module.HudModule
2529import com.lambda.module.ModuleRegistry
30+ import com.lambda.module.modules.client.GuiSettings
31+ import com.lambda.module.modules.client.ClickGui
32+ import imgui.ImGui
33+ import imgui.ImDrawList
34+ import imgui.flag.ImDrawListFlags
2635import imgui.flag.ImGuiWindowFlags
36+ import kotlin.math.PI
2737
2838object HudGuiLayout : Loadable {
2939 const val DEFAULT_HUD_FLAGS =
3040 ImGuiWindowFlags .NoDecoration or
3141 ImGuiWindowFlags .NoBackground or
3242 ImGuiWindowFlags .AlwaysAutoResize or
3343 ImGuiWindowFlags .NoDocking
44+ private var activeDragHudName: String? = null
45+ private var mouseWasDown = false
46+ private var dragOffsetX = 0f
47+ private var dragOffsetY = 0f
48+ private val lastBounds = mutableMapOf<String , RectF >()
49+ private val pendingPositions = mutableMapOf<String , Pair <Float , Float >>()
50+ private val snapOverlays = mutableMapOf<String , SnapVisual >()
51+
52+ private data class SnapVisual (
53+ val snapX : Float? ,
54+ val snapY : Float? ,
55+ val kindX : Guide .Kind ? ,
56+ val kindY : Guide .Kind ?
57+ )
58+
59+ private const val PI_F = PI .toFloat()
60+ private const val HALF_PI_F = (0.5f * PI ).toFloat()
61+ private const val THREE_HALVES_PI_F = (1.5f * PI ).toFloat()
62+ private const val TWO_PI_F = (2f * PI ).toFloat()
3463
3564 init {
3665 listen<GuiEvent .NewFrame > {
3766 buildLayout {
38- ModuleRegistry .modules
67+ val vp = ImGui .getMainViewport()
68+ SnapManager .beginFrame(vp.sizeX, vp.sizeY, io.fontGlobalScale)
69+
70+ val mouseDown = io.mouseDown[0 ]
71+ val mousePressedThisFrame = mouseDown && ! mouseWasDown
72+ val mouseReleasedThisFrame = ! mouseDown && mouseWasDown
73+ mouseWasDown = mouseDown
74+ if (mouseReleasedThisFrame) {
75+ activeDragHudName = null
76+ }
77+
78+ pendingPositions.clear()
79+ snapOverlays.clear()
80+
81+ val (huds, notShown) = ModuleRegistry .modules
3982 .filterIsInstance<HudModule >()
40- .filter { it.isEnabled }
41- .forEach { hud ->
42- window(" ##${hud.name} " , flags = DEFAULT_HUD_FLAGS ) {
43- with (hud) { buildLayout() }
83+ .partition { it.isEnabled }
84+
85+ notShown.forEach { SnapManager .unregisterElement(it.name) }
86+
87+ if (ClickGui .isEnabled && activeDragHudName == null && mousePressedThisFrame) {
88+ tryBeginDrag(huds)
89+ }
90+
91+ if (ClickGui .isEnabled && activeDragHudName != null && mouseDown) {
92+ updateDragAndSnapping()
93+ }
94+
95+ huds.forEach { hud ->
96+ val override = pendingPositions[hud.name]
97+ if (override != null ) {
98+ ImGui .setNextWindowPos(override .first, override .second)
99+ }
100+ window(" ##${hud.name} " , flags = DEFAULT_HUD_FLAGS ) {
101+ val vis = snapOverlays[hud.name]
102+ if (vis != null ) {
103+ SnapManager .drawSnapLines(
104+ foregroundDrawList,
105+ vis.snapX, vis.kindX,
106+ vis.snapY, vis.kindY
107+ )
108+ }
109+ with (hud) { buildLayout() }
110+ if (ClickGui .isEnabled) {
111+ drawHudOutline(foregroundDrawList, windowPos.x, windowPos.y, windowSize.x, windowSize.y)
44112 }
113+ val rect = RectF (windowPos.x, windowPos.y, windowSize.x, windowSize.y)
114+ SnapManager .registerElement(hud.name, rect)
115+ lastBounds[hud.name] = rect
45116 }
117+ }
118+ }
119+ }
120+ }
121+
122+ private fun ImGuiBuilder.tryBeginDrag (huds : List <HudModule >) {
123+ val mx = io.mousePos.x
124+ val my = io.mousePos.y
125+ huds.forEach { hud ->
126+ val r = lastBounds[hud.name] ? : return @forEach
127+ val inside = mx >= r.x && mx <= r.x + r.w && my >= r.y && my <= r.y + r.h
128+ if (inside) {
129+ activeDragHudName = hud.name
130+ dragOffsetX = mx - r.x
131+ dragOffsetY = my - r.y
132+ return
46133 }
47134 }
48135 }
136+
137+ private fun ImGuiBuilder.updateDragAndSnapping () {
138+ val id = activeDragHudName ? : return
139+ val last = lastBounds[id] ? : return
140+ val mx = io.mousePos.x
141+ val my = io.mousePos.y
142+ val targetX = mx - dragOffsetX
143+ val targetY = my - dragOffsetY
144+ val proposed = RectF (targetX, targetY, last.w, last.h)
145+ val snap = SnapManager .computeSnap(proposed, id)
146+ val finalX = targetX + snap.dx
147+ val finalY = targetY + snap.dy
148+ pendingPositions[id] = finalX to finalY
149+ snapOverlays[id] = SnapVisual (snap.snapX, snap.snapY, snap.kindX, snap.kindY)
150+ }
151+
152+ private fun ImGuiBuilder.drawHudOutline (draw : ImDrawList , x : Float , y : Float , w : Float , h : Float ) {
153+ val baseRadius = GuiSettings .hudOutlineCornerRadius
154+ val rounding = if (baseRadius > 0f ) baseRadius else style.windowRounding
155+ val inflate = GuiSettings .hudOutlineCornerInflate
156+ // Soft halo corners (gray, slightly smaller)
157+ drawCornerArcs(
158+ draw,
159+ x, y, w, h,
160+ (rounding + inflate).coerceAtLeast(0f ),
161+ GuiSettings .hudOutlineHaloColor.rgb,
162+ GuiSettings .hudOutlineHaloThickness
163+ )
164+ // Crisp inner corner arcs
165+ drawCornerArcs(
166+ draw,
167+ x, y, w, h,
168+ rounding.coerceAtLeast(0f ),
169+ GuiSettings .hudOutlineBorderColor.rgb,
170+ GuiSettings .hudOutlineBorderThickness
171+ )
172+ }
173+
174+ private fun drawCornerArcs (
175+ draw : ImDrawList ,
176+ x : Float , y : Float , w : Float , h : Float ,
177+ radius : Float ,
178+ color : Int ,
179+ thickness : Float
180+ ) {
181+ if (radius <= 0f || thickness <= 0f ) return
182+ val tlCx = x + radius
183+ val tlCy = y + radius
184+ val trCx = x + w - radius
185+ val trCy = y + radius
186+ val brCx = x + w - radius
187+ val brCy = y + h - radius
188+ val blCx = x + radius
189+ val blCy = y + h - radius
190+
191+ fun strokeArc (cx : Float , cy : Float , start : Float , end : Float ) {
192+ draw.pathClear()
193+ draw.pathArcTo(cx, cy, radius, start, end, 0 )
194+ draw.pathStroke(color, ImDrawListFlags .None , thickness)
195+ }
196+
197+ // TL: pi -> 1.5pi
198+ strokeArc(tlCx, tlCy, PI_F , THREE_HALVES_PI_F )
199+ // TR: 1.5pi -> 2pi
200+ strokeArc(trCx, trCy, THREE_HALVES_PI_F , TWO_PI_F )
201+ // BR: 0 -> 0.5pi
202+ strokeArc(brCx, brCy, 0f , HALF_PI_F )
203+ // BL: 0.5pi -> pi
204+ strokeArc(blCx, blCy, HALF_PI_F , PI_F )
205+ }
49206}
0 commit comments