Skip to content

Commit 64bf167

Browse files
committed
Popup windows
1 parent db1acda commit 64bf167

File tree

16 files changed

+269
-36
lines changed

16 files changed

+269
-36
lines changed

common/src/main/kotlin/com/lambda/graphics/renderer/gui/rect/FilledRectRenderer.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ object FilledRectRenderer : AbstractGUIRenderer(
2929
) {
3030
private const val MIN_SIZE = 0.5
3131
private const val MIN_ALPHA = 1
32-
private const val EXPAND = 0.3
32+
private const val EXPAND = 0.35
3333

3434
fun filledRect(
3535
rect: Rect,

common/src/main/kotlin/com/lambda/gui/GuiManager.kt

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -21,15 +21,17 @@ import com.lambda.config.settings.NumericSetting
2121
import com.lambda.config.settings.comparable.BooleanSetting
2222
import com.lambda.config.settings.comparable.EnumSetting
2323
import com.lambda.config.settings.FunctionSetting
24+
import com.lambda.config.settings.complex.ColorSetting
2425
import com.lambda.config.settings.complex.KeyBindSetting
2526
import com.lambda.core.Loadable
2627
import com.lambda.gui.component.core.UIBuilder
2728
import com.lambda.gui.component.layout.Layout
28-
import com.lambda.gui.impl.clickgui.module.settings.BooleanButton.Companion.booleanSetting
29-
import com.lambda.gui.impl.clickgui.module.settings.EnumSlider.Companion.enumSetting
30-
import com.lambda.gui.impl.clickgui.module.settings.KeybindPicker.Companion.keybindSetting
31-
import com.lambda.gui.impl.clickgui.module.settings.NumberSlider.Companion.numericSetting
32-
import com.lambda.gui.impl.clickgui.module.settings.UnitButton.Companion.unitSetting
29+
import com.lambda.gui.impl.clickgui.module.settings.impl.BooleanButton.Companion.booleanSetting
30+
import com.lambda.gui.impl.clickgui.module.settings.impl.ColorPicker.Companion.colorPicker
31+
import com.lambda.gui.impl.clickgui.module.settings.impl.EnumSlider.Companion.enumSetting
32+
import com.lambda.gui.impl.clickgui.module.settings.impl.KeybindPicker.Companion.keybindSetting
33+
import com.lambda.gui.impl.clickgui.module.settings.impl.NumberSlider.Companion.numericSetting
34+
import com.lambda.gui.impl.clickgui.module.settings.impl.UnitButton.Companion.unitSetting
3335
import kotlin.reflect.KClass
3436

3537
object GuiManager : Loadable {
@@ -60,6 +62,10 @@ object GuiManager : Loadable {
6062
owner.keybindSetting(ref)
6163
}
6264

65+
typeAdapter<ColorSetting> { owner, ref ->
66+
owner.colorPicker(ref)
67+
}
68+
6369
return "Loaded ${typeMap.size} gui type adapters."
6470
}
6571

common/src/main/kotlin/com/lambda/gui/component/layout/Layout.kt

Lines changed: 24 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import com.lambda.graphics.RenderMain
2121
import com.lambda.graphics.animation.AnimationTicker
2222
import com.lambda.event.events.GuiEvent
2323
import com.lambda.graphics.pipeline.ScissorAdapter
24+
import com.lambda.gui.ScreenLayout
2425
import com.lambda.gui.component.HAlign
2526
import com.lambda.gui.component.VAlign
2627
import com.lambda.gui.component.core.*
@@ -118,12 +119,22 @@ open class Layout(
118119
// Structure
119120
val children = mutableListOf<Layout>()
120121
var selectedChild: Layout? = null
122+
val root = run {
123+
var own: Layout = owner ?: this
124+
125+
while (true) {
126+
own = own.owner ?: break
127+
}
128+
129+
own as? ScreenLayout ?: throw IllegalStateException("Root layout is not a ScreenLayout class")
130+
}
131+
121132
protected open val renderSelf: Boolean get() = width > 1 && height > 1
122133
protected open val scissorRect get() = rect
123134

124135
// Inputs
125136
protected var mousePosition = Vec2d.ZERO; set(value) {
126-
if (field == value) return
137+
//if (field == value) return
127138
field = value
128139

129140
selectedChild = if (isHovered) children.lastOrNull {
@@ -157,8 +168,9 @@ open class Layout(
157168
* @param action The action to be performed.
158169
*/
159170
@LayoutBuilder
160-
fun <T : Layout> T.use(action: T.() -> Unit) {
171+
fun <T : Layout> T.use(action: T.() -> Unit): T {
161172
action(this)
173+
return this@use
162174
}
163175

164176
/**
@@ -289,18 +301,6 @@ open class Layout(
289301
}
290302
}
291303

292-
init {
293-
onUpdate { // Update the layout
294-
screenSize = RenderMain.screenSize
295-
296-
// Update relative position and bounds
297-
ownerX = owner?.positionX ?: ownerX
298-
ownerY = owner?.positionY ?: ownerY
299-
ownerWidth = owner?.width ?: screenSize.x
300-
ownerHeight = owner?.height ?: screenSize.y
301-
}
302-
}
303-
304304
fun onEvent(e: GuiEvent) {
305305
// Update self
306306
when (e) {
@@ -314,6 +314,8 @@ open class Layout(
314314
hideActions.forEach { it(this) }
315315
}
316316
is GuiEvent.Tick -> {
317+
// hack to update hover state once a tick if not moving the mouse
318+
mousePosition = mousePosition
317319
tickActions.forEach { it(this) }
318320
}
319321
is GuiEvent.KeyPress -> {
@@ -323,6 +325,14 @@ open class Layout(
323325
charTypedActions.forEach { it(this, e.char) }
324326
}
325327
is GuiEvent.Update -> {
328+
screenSize = RenderMain.screenSize
329+
330+
// Update relative position and bounds
331+
ownerX = owner?.positionX ?: ownerX
332+
ownerY = owner?.positionY ?: ownerY
333+
ownerWidth = owner?.width ?: screenSize.x
334+
ownerHeight = owner?.height ?: screenSize.y
335+
326336
updateActions.forEach { it(this) }
327337
}
328338
is GuiEvent.Render -> {
Lines changed: 159 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,159 @@
1+
/*
2+
* Copyright 2025 Lambda
3+
*
4+
* This program is free software: you can redistribute it and/or modify
5+
* it under the terms of the GNU General Public License as published by
6+
* the Free Software Foundation, either version 3 of the License, or
7+
* (at your option) any later version.
8+
*
9+
* This program is distributed in the hope that it will be useful,
10+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
11+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12+
* GNU General Public License for more details.
13+
*
14+
* You should have received a copy of the GNU General Public License
15+
* along with this program. If not, see <http://www.gnu.org/licenses/>.
16+
*/
17+
18+
package com.lambda.gui.component.popup
19+
20+
import com.lambda.event.events.GuiEvent
21+
import com.lambda.graphics.animation.Animation.Companion.exp
22+
import com.lambda.gui.component.core.FilledRect.Companion.rect
23+
import com.lambda.gui.component.layout.Layout
24+
import com.lambda.gui.component.layout.Layout.Companion.animationTicker
25+
import com.lambda.gui.component.layout.Layout.Companion.layout
26+
import com.lambda.gui.component.window.Window
27+
import com.lambda.gui.component.window.Window.Companion.window
28+
import com.lambda.module.modules.client.ClickGui.backgroundTint
29+
import com.lambda.util.Mouse
30+
import com.lambda.util.math.multAlpha
31+
32+
open class Popup(
33+
requestor: Layout,
34+
title: String
35+
) {
36+
private val baseHider = LayoutHideService { base }
37+
protected val windowHider = LayoutHideService { window }
38+
39+
private val animation = requestor.animationTicker()
40+
private val tintAnimation by animation.exp(0.0, 1.0, 0.6) { windowHider.isShown }
41+
private val root = requestor.root
42+
43+
private val base: Layout = root.layout {
44+
rect {
45+
onUpdate {
46+
width = this@layout.width
47+
height = this@layout.height
48+
setColor(backgroundTint.multAlpha(tintAnimation))
49+
}
50+
}
51+
52+
onUpdate {
53+
properties.interactionPassthrough = !windowHider.isShown
54+
}
55+
}.apply {
56+
destroy()
57+
}
58+
59+
/**
60+
* The shown window on the popup screen
61+
*/
62+
val window = base.window(
63+
title = title,
64+
draggable = false,
65+
scrollable = false,
66+
minimizing = Window.Minimizing.Disabled,
67+
resizable = false,
68+
autoResize = Window.AutoResize.Disabled
69+
).apply {
70+
destroy()
71+
}
72+
73+
fun show() {
74+
windowHider.isShown = true
75+
baseHider.isShown = true
76+
window.onEvent(GuiEvent.Update)
77+
}
78+
79+
open fun hide() {
80+
windowHider.isShown = false
81+
}
82+
83+
init {
84+
requestor.apply {
85+
root.onMouse(action = Mouse.Action.Click) {
86+
if (!window.isHovered) hide()
87+
}
88+
89+
root.onTick {
90+
if (tintAnimation == 0.0 && !windowHider.isShown)
91+
baseHider.isShown = false
92+
}
93+
94+
root.onUpdate {
95+
base.width = root.width
96+
base.height = root.height
97+
window.positionX = (base.width - window.width) * 0.5
98+
window.positionY = (base.height - window.height) * 0.5 - window.titleBar.height
99+
100+
baseHider.update()
101+
windowHider.update()
102+
}
103+
}
104+
}
105+
106+
abstract class Returnable <V : Any>(
107+
requestor: Layout,
108+
title: String
109+
) : Popup(requestor, title) {
110+
private var receiver: ((V) -> Unit)? = null
111+
protected abstract fun buildOutput(): V
112+
113+
override fun hide() {
114+
if (windowHider.isShown) {
115+
receiver?.invoke(buildOutput())
116+
receiver = null
117+
}
118+
super.hide()
119+
}
120+
121+
companion object {
122+
/**
123+
* Shows the given [popupLayout].
124+
*
125+
* @param block The action to run on the popup exit.
126+
*/
127+
fun <V: Any> popup(popupLayout: Returnable<V>, block: (V) -> Unit) = popupLayout.apply {
128+
receiver = block
129+
show()
130+
}
131+
}
132+
}
133+
134+
protected class LayoutHideService(val layoutBlock: () -> Layout) {
135+
private var lastShown = false
136+
var isShown = false
137+
138+
fun update() {
139+
val layout = layoutBlock()
140+
val owner = layout.owner ?: throw IllegalStateException(
141+
"Cannot hide root layout."
142+
)
143+
144+
val prev = lastShown
145+
val value = isShown
146+
lastShown = value
147+
148+
if (prev == value) return
149+
150+
if (value) {
151+
owner.children += layout
152+
layout.onEvent(GuiEvent.Show)
153+
} else {
154+
owner.children -= layout
155+
layout.onEvent(GuiEvent.Hide)
156+
}
157+
}
158+
}
159+
}

common/src/main/kotlin/com/lambda/gui/component/window/Window.kt

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -126,7 +126,12 @@ open class Window(
126126
get() = !isMinimized
127127
set(value) { isMinimized = !value }
128128

129-
var windowWidth = initialSize.x
129+
var windowWidth = initialSize.x; set(value) {
130+
if (field == value) return
131+
field = value
132+
133+
if (resizeX == null) widthAnimation = value
134+
}
130135
var windowHeight = initialSize.y
131136

132137
var widthAnimation by animation.exp(0.8, ::windowWidth)

common/src/main/kotlin/com/lambda/gui/impl/clickgui/core/AnimatedChild.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ import com.lambda.graphics.animation.Animation.Companion.exp
2121
import com.lambda.gui.component.HAlign
2222
import com.lambda.gui.component.layout.Layout
2323
import com.lambda.gui.component.window.Window
24-
import com.lambda.gui.impl.clickgui.module.SettingLayout
24+
import com.lambda.gui.impl.clickgui.module.settings.SettingLayout
2525
import com.lambda.module.modules.client.ClickGui
2626
import com.lambda.util.math.MathUtils.toInt
2727
import com.lambda.util.math.Vec2d

common/src/main/kotlin/com/lambda/gui/impl/clickgui/module/ModuleLayout.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ import com.lambda.gui.component.layout.Layout
2929
import com.lambda.gui.component.window.Window
3030
import com.lambda.gui.impl.clickgui.ModuleWindow
3131
import com.lambda.gui.impl.clickgui.core.AnimatedChild
32+
import com.lambda.gui.impl.clickgui.module.settings.SettingLayout
3233
import com.lambda.util.Mouse
3334
import com.lambda.util.math.*
3435
import java.awt.Color

common/src/main/kotlin/com/lambda/gui/impl/clickgui/module/SettingLayout.kt renamed to common/src/main/kotlin/com/lambda/gui/impl/clickgui/module/settings/SettingLayout.kt

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
* along with this program. If not, see <http://www.gnu.org/licenses/>.
1616
*/
1717

18-
package com.lambda.gui.impl.clickgui.module
18+
package com.lambda.gui.impl.clickgui.module.settings
1919

2020
import com.lambda.config.AbstractSetting
2121
import com.lambda.module.modules.client.ClickGui
@@ -30,7 +30,7 @@ import com.lambda.util.math.*
3030
abstract class SettingLayout <V : Any, T: AbstractSetting<V>> (
3131
owner: Layout,
3232
val setting: T,
33-
private val expandable: Boolean = false
33+
expandable: Boolean = false
3434
) : AnimatedChild(
3535
owner,
3636
setting.name,
@@ -47,8 +47,6 @@ abstract class SettingLayout <V : Any, T: AbstractSetting<V>> (
4747

4848
override val isShown: Boolean get() = super.isShown && isVisible
4949

50-
51-
5250
init {
5351
isMinimized = true
5452

common/src/main/kotlin/com/lambda/gui/impl/clickgui/module/settings/SettingSlider.kt

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,6 @@ import com.lambda.gui.component.VAlign
2424
import com.lambda.gui.component.core.TextField.Companion.textField
2525
import com.lambda.gui.component.layout.Layout
2626
import com.lambda.gui.impl.clickgui.core.SliderLayout.Companion.sliderBehind
27-
import com.lambda.gui.impl.clickgui.module.SettingLayout
2827
import com.lambda.module.modules.client.ClickGui
2928
import com.lambda.util.math.lerp
3029

common/src/main/kotlin/com/lambda/gui/impl/clickgui/module/settings/BooleanButton.kt renamed to common/src/main/kotlin/com/lambda/gui/impl/clickgui/module/settings/impl/BooleanButton.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
* along with this program. If not, see <http://www.gnu.org/licenses/>.
1616
*/
1717

18-
package com.lambda.gui.impl.clickgui.module.settings
18+
package com.lambda.gui.impl.clickgui.module.settings.impl
1919

2020
import com.lambda.config.settings.comparable.BooleanSetting
2121
import com.lambda.graphics.animation.Animation.Companion.exp
@@ -24,7 +24,7 @@ import com.lambda.gui.component.core.FilledRect.Companion.rect
2424
import com.lambda.gui.component.core.OutlineRect.Companion.outline
2525
import com.lambda.gui.component.core.UIBuilder
2626
import com.lambda.gui.component.layout.Layout
27-
import com.lambda.gui.impl.clickgui.module.SettingLayout
27+
import com.lambda.gui.impl.clickgui.module.settings.SettingLayout
2828
import com.lambda.util.Mouse
2929
import com.lambda.util.math.Rect
3030
import com.lambda.util.math.Vec2d

0 commit comments

Comments
 (0)