@@ -20,30 +20,195 @@ 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+ // Precomputed Float PI values to avoid repeated conversions
60+ private const val PI_F = PI .toFloat()
61+ private const val HALF_PI_F = (0.5f * PI ).toFloat()
62+ private const val THREE_HALVES_PI_F = (1.5f * PI ).toFloat()
63+ private const val TWO_PI_F = (2f * PI ).toFloat()
3464
3565 init {
3666 listen<GuiEvent .NewFrame > {
3767 buildLayout {
38- ModuleRegistry .modules
68+ val vp = ImGui .getMainViewport()
69+ SnapManager .beginFrame(vp.sizeX, vp.sizeY, io.fontGlobalScale)
70+
71+ val mouseDown = io.mouseDown[0 ]
72+ val mousePressedThisFrame = mouseDown && ! mouseWasDown
73+ val mouseReleasedThisFrame = ! mouseDown && mouseWasDown
74+ mouseWasDown = mouseDown
75+ if (mouseReleasedThisFrame) {
76+ activeDragHudName = null
77+ }
78+
79+ pendingPositions.clear()
80+ snapOverlays.clear()
81+
82+ val huds = ModuleRegistry .modules
3983 .filterIsInstance<HudModule >()
4084 .filter { it.isEnabled }
41- .forEach { hud ->
42- window(" ##${hud.name} " , flags = DEFAULT_HUD_FLAGS ) {
43- with (hud) { buildLayout() }
85+
86+ if (ClickGui .isEnabled && activeDragHudName == null && mousePressedThisFrame) {
87+ tryBeginDrag(huds)
88+ }
89+
90+ if (ClickGui .isEnabled && activeDragHudName != null && mouseDown) {
91+ updateDragAndSnapping()
92+ }
93+
94+ huds.forEach { hud ->
95+ val override = pendingPositions[hud.name]
96+ if (override != null ) {
97+ ImGui .setNextWindowPos(override .first, override .second)
98+ }
99+ window(" ##${hud.name} " , flags = DEFAULT_HUD_FLAGS ) {
100+ val vis = snapOverlays[hud.name]
101+ if (vis != null ) {
102+ SnapManager .drawSnapLines(
103+ foregroundDrawList,
104+ vis.snapX, vis.kindX,
105+ vis.snapY, vis.kindY
106+ )
44107 }
108+ with (hud) { buildLayout() }
109+ // Rounded-corner only outline; pull parameters from settings
110+ if (ClickGui .isEnabled) {
111+ drawHudOutline(
112+ draw = foregroundDrawList,
113+ x = windowPos.x,
114+ y = windowPos.y,
115+ w = windowSize.x,
116+ h = windowSize.y
117+ )
118+ }
119+ val p = windowPos
120+ val s = windowSize
121+ val rect = RectF (p.x, p.y, s.x, s.y)
122+ SnapManager .registerElement(hud.name, rect)
123+ lastBounds[hud.name] = rect
45124 }
125+ }
46126 }
47127 }
48128 }
129+
130+ private fun ImGuiBuilder.tryBeginDrag (huds : List <HudModule >) {
131+ val mx = io.mousePos.x
132+ val my = io.mousePos.y
133+ huds.forEach { hud ->
134+ val r = lastBounds[hud.name] ? : return @forEach
135+ val inside = mx >= r.x && mx <= r.x + r.w && my >= r.y && my <= r.y + r.h
136+ if (inside) {
137+ activeDragHudName = hud.name
138+ dragOffsetX = mx - r.x
139+ dragOffsetY = my - r.y
140+ return
141+ }
142+ }
143+ }
144+
145+ private fun ImGuiBuilder.updateDragAndSnapping () {
146+ val id = activeDragHudName ? : return
147+ val last = lastBounds[id] ? : return
148+ val mx = io.mousePos.x
149+ val my = io.mousePos.y
150+ val targetX = mx - dragOffsetX
151+ val targetY = my - dragOffsetY
152+ val proposed = RectF (targetX, targetY, last.w, last.h)
153+ val snap = SnapManager .computeSnap(proposed, id)
154+ val finalX = targetX + snap.dx
155+ val finalY = targetY + snap.dy
156+ pendingPositions[id] = finalX to finalY
157+ snapOverlays[id] = SnapVisual (snap.snapX, snap.snapY, snap.kindX, snap.kindY)
158+ }
159+
160+ private fun ImGuiBuilder.drawHudOutline (draw : ImDrawList , x : Float , y : Float , w : Float , h : Float ) {
161+ val baseRadius = GuiSettings .hudOutlineCornerRadius
162+ val rounding = if (baseRadius > 0f ) baseRadius else style.windowRounding
163+ val inflate = GuiSettings .hudOutlineCornerInflate
164+ // Soft halo corners (gray, slightly smaller)
165+ drawCornerArcs(
166+ draw,
167+ x, y, w, h,
168+ (rounding + inflate).coerceAtLeast(0f ),
169+ GuiSettings .hudOutlineHaloColor.rgb,
170+ GuiSettings .hudOutlineHaloThickness
171+ )
172+ // Crisp inner corner arcs
173+ drawCornerArcs(
174+ draw,
175+ x, y, w, h,
176+ rounding.coerceAtLeast(0f ),
177+ GuiSettings .hudOutlineBorderColor.rgb,
178+ GuiSettings .hudOutlineBorderThickness
179+ )
180+ }
181+
182+ private fun drawCornerArcs (
183+ draw : ImDrawList ,
184+ x : Float , y : Float , w : Float , h : Float ,
185+ radius : Float ,
186+ color : Int ,
187+ thickness : Float
188+ ) {
189+ if (radius <= 0f || thickness <= 0f ) return
190+ val tlCx = x + radius
191+ val tlCy = y + radius
192+ val trCx = x + w - radius
193+ val trCy = y + radius
194+ val brCx = x + w - radius
195+ val brCy = y + h - radius
196+ val blCx = x + radius
197+ val blCy = y + h - radius
198+
199+ fun strokeArc (cx : Float , cy : Float , start : Float , end : Float ) {
200+ draw.pathClear()
201+ draw.pathArcTo(cx, cy, radius, start, end, 0 )
202+ draw.pathStroke(color, ImDrawListFlags .None , thickness)
203+ }
204+
205+ // TL: pi -> 1.5pi
206+ strokeArc(tlCx, tlCy, PI_F , THREE_HALVES_PI_F )
207+ // TR: 1.5pi -> 2pi
208+ strokeArc(trCx, trCy, THREE_HALVES_PI_F , TWO_PI_F )
209+ // BR: 0 -> 0.5pi
210+ strokeArc(brCx, brCy, 0f , HALF_PI_F )
211+ // BL: 0.5pi -> pi
212+ strokeArc(blCx, blCy, HALF_PI_F , PI_F )
213+ }
49214}
0 commit comments