Skip to content

Commit f9f3be2

Browse files
committed
Fixes to cloud settings, Added controls settings, fixed slider element and mouse capturing for it.
1 parent 371dc9a commit f9f3be2

File tree

8 files changed

+1100
-38
lines changed

8 files changed

+1100
-38
lines changed

src/main/java/de/bixilon/minosoft/gui/rendering/gui/elements/input/slider/SliderElement.kt

Lines changed: 103 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -14,16 +14,18 @@
1414
package de.bixilon.minosoft.gui.rendering.gui.elements.input.slider
1515

1616
import de.bixilon.kmath.vec.vec2.f.Vec2f
17-
import de.bixilon.kmath.vec.vec2.i.Vec2i
1817
import de.bixilon.minosoft.data.registries.identified.Namespaces.minecraft
1918
import de.bixilon.minosoft.gui.rendering.gui.GUIRenderer
2019
import de.bixilon.minosoft.gui.rendering.gui.elements.Element
2120
import de.bixilon.minosoft.gui.rendering.gui.elements.primitive.AtlasImageElement
2221
import de.bixilon.minosoft.gui.rendering.gui.elements.text.TextElement
22+
import de.bixilon.minosoft.gui.rendering.gui.input.MouseCapturing
23+
import de.bixilon.minosoft.gui.rendering.gui.mesh.GUIVertexOptions.Companion.copy
2324
import de.bixilon.minosoft.gui.rendering.gui.input.mouse.MouseActions
2425
import de.bixilon.minosoft.gui.rendering.gui.input.mouse.MouseButtons
2526
import de.bixilon.minosoft.gui.rendering.gui.mesh.GUIVertexOptions
2627
import de.bixilon.minosoft.gui.rendering.gui.mesh.consumer.GuiVertexConsumer
28+
import de.bixilon.minosoft.gui.rendering.system.window.CursorShapes
2729
import kotlin.math.roundToInt
2830

2931
class SliderElement(
@@ -33,12 +35,22 @@ class SliderElement(
3335
private val max: Float,
3436
initialValue: Float,
3537
private val onChange: (Float) -> Unit
36-
) : Element(guiRenderer) {
38+
) : Element(guiRenderer), MouseCapturing {
3739
private val buttonAtlas = guiRenderer.atlas[BUTTON_ATLAS]
38-
private val sliderAtlas = guiRenderer.atlas[SLIDER_ATLAS]
3940

4041
var textElement: TextElement
4142
private var isDragging = false
43+
private var isHovered = false
44+
private var isHandleHovered = false
45+
46+
var disabled: Boolean = false
47+
set(value) {
48+
if (field == value) return
49+
field = value
50+
cacheUpToDate = false
51+
}
52+
53+
override val isCapturingMouse: Boolean get() = isDragging
4254

4355
var value: Float = initialValue.coerceIn(min, max)
4456
set(value) {
@@ -54,8 +66,7 @@ class SliderElement(
5466
init {
5567
textElement = TextElement(guiRenderer, getDisplayText(), background = null, parent = this)
5668
updateText()
57-
// Set initial size based on text element size with padding (similar to ButtonElement)
58-
size = Vec2f(textElement.size.x + TEXT_PADDING * 2, 20.0f)
69+
size = Vec2f(textElement.size.x + TEXT_PADDING * 2, SLIDER_HEIGHT)
5970
}
6071

6172
private fun getDisplayText(): String {
@@ -66,7 +77,7 @@ class SliderElement(
6677
textElement.text = getDisplayText()
6778
textElement.silentApply()
6879
if (size.x == 0.0f) {
69-
size = Vec2f(textElement.size.x + TEXT_PADDING * 2, 20.0f)
80+
size = Vec2f(textElement.size.x + TEXT_PADDING * 2, SLIDER_HEIGHT)
7081
}
7182
}
7283

@@ -77,26 +88,40 @@ class SliderElement(
7788

7889
override fun forceRender(offset: Vec2f, consumer: GuiVertexConsumer, options: GUIVertexOptions?) {
7990
val size = size
80-
val sliderHeight = 20.0f
91+
val renderOptions = if (disabled) options.copy(alpha = 0.4f) else options
8192

82-
val background = AtlasImageElement(guiRenderer, buttonAtlas?.get("normal") ?: guiRenderer.context.textures.whiteTexture)
83-
background.size = size
84-
background.render(offset, consumer, options)
93+
val trackTexture = buttonAtlas?.get("disabled") ?: guiRenderer.context.textures.whiteTexture
94+
val trackBackground = AtlasImageElement(guiRenderer, trackTexture)
95+
trackBackground.size = size
96+
trackBackground.render(offset, consumer, renderOptions)
8597

8698
val normalizedValue = (value - min) / (max - min)
87-
val sliderWidth = 8.0f
88-
val trackWidth = size.x - sliderWidth
89-
val sliderX = trackWidth * normalizedValue
90-
val slider = AtlasImageElement(guiRenderer, buttonAtlas?.get("hovered") ?: guiRenderer.context.textures.whiteTexture)
91-
slider.size = Vec2f(sliderWidth, sliderHeight)
92-
slider.render(offset + Vec2f(sliderX, 0.0f), consumer, options)
99+
val handleWidth = HANDLE_WIDTH
100+
val trackWidth = size.x - handleWidth
101+
val handleX = trackWidth * normalizedValue
102+
103+
val handleTexture = if (disabled) {
104+
buttonAtlas?.get("disabled")
105+
} else if (isHandleHovered || isDragging) {
106+
buttonAtlas?.get("hovered")
107+
} else {
108+
buttonAtlas?.get("normal")
109+
} ?: guiRenderer.context.textures.whiteTexture
110+
111+
val slider = AtlasImageElement(guiRenderer, handleTexture)
112+
slider.size = Vec2f(handleWidth, SLIDER_HEIGHT)
113+
slider.render(offset + Vec2f(handleX, 0.0f), consumer, renderOptions)
114+
93115
val textSize = textElement.size
94116
val textX = (size.x - textSize.x) / 2
95117
val textY = (size.y - textSize.y) / 2
96-
textElement.render(offset + Vec2f(textX, textY), consumer, options)
118+
textElement.render(offset + Vec2f(textX, textY), consumer, renderOptions)
97119
}
98120

99121
override fun onMouseAction(position: Vec2f, button: MouseButtons, action: MouseActions, count: Int): Boolean {
122+
if (disabled) {
123+
return true
124+
}
100125
if (button != MouseButtons.LEFT) {
101126
return true
102127
}
@@ -105,9 +130,12 @@ class SliderElement(
105130
MouseActions.PRESS -> {
106131
isDragging = true
107132
updateValueFromPosition(position.x)
133+
cacheUpToDate = false
108134
}
109135
MouseActions.RELEASE -> {
110136
isDragging = false
137+
context.window.resetCursor()
138+
cacheUpToDate = false
111139
}
112140
}
113141

@@ -118,31 +146,84 @@ class SliderElement(
118146
if (isDragging) {
119147
updateValueFromPosition(position.x)
120148
}
149+
150+
if (!isDragging) {
151+
val normalizedValue = (value - min) / (max - min)
152+
val handleWidth = HANDLE_WIDTH
153+
val trackWidth = size.x - handleWidth
154+
val handleX = trackWidth * normalizedValue
155+
156+
val wasHandleHovered = isHandleHovered
157+
isHandleHovered = position.x >= handleX && position.x < handleX + handleWidth
158+
159+
if (wasHandleHovered != isHandleHovered) {
160+
cacheUpToDate = false
161+
}
162+
}
163+
164+
return true
165+
}
166+
167+
override fun onMouseEnter(position: Vec2f, absolute: Vec2f): Boolean {
168+
isHovered = true
169+
context.window.cursorShape = CursorShapes.HAND
170+
171+
val normalizedValue = (value - min) / (max - min)
172+
val handleWidth = HANDLE_WIDTH
173+
val trackWidth = size.x - handleWidth
174+
val handleX = trackWidth * normalizedValue
175+
176+
isHandleHovered = position.x >= handleX && position.x < handleX + handleWidth
177+
cacheUpToDate = false
121178
return true
122179
}
123180

124181
override fun onMouseLeave(): Boolean {
125-
isDragging = false
182+
isHovered = false
183+
isHandleHovered = false
184+
if (!isDragging) {
185+
context.window.resetCursor()
186+
}
187+
cacheUpToDate = false
126188
return super.onMouseLeave()
127189
}
190+
191+
override fun onMouseActionOutside(relativeX: Float, button: MouseButtons, action: MouseActions): Boolean {
192+
if (!isDragging) return false
193+
194+
if (button == MouseButtons.LEFT && action == MouseActions.RELEASE) {
195+
isDragging = false
196+
context.window.resetCursor()
197+
cacheUpToDate = false
198+
return true
199+
}
200+
return false
201+
}
202+
203+
override fun onMouseMoveOutside(relativeX: Float): Boolean {
204+
if (!isDragging) return false
205+
updateValueFromPosition(relativeX)
206+
return true
207+
}
128208

129209
private fun updateValueFromPosition(x: Float) {
130-
val sliderWidth = 8.0f
131-
val trackWidth = size.x - sliderWidth
210+
val handleWidth = HANDLE_WIDTH
211+
val trackWidth = size.x - handleWidth
132212

133213
if (trackWidth <= 0) {
134214
value = min
135215
return
136216
}
137217

138-
val normalizedX = (x - sliderWidth / 2) / trackWidth
218+
val normalizedX = (x - handleWidth / 2) / trackWidth
139219
value = min + normalizedX * (max - min)
140220
}
141221

142222
companion object {
143223
val BUTTON_ATLAS = minecraft("elements/button")
144-
val SLIDER_ATLAS = minecraft("elements/slider")
145224
private const val TEXT_PADDING = 4.0f
225+
private const val SLIDER_HEIGHT = 20.0f
226+
private const val HANDLE_WIDTH = 8.0f
146227
}
147228
}
148229

src/main/java/de/bixilon/minosoft/gui/rendering/gui/gui/screen/menu/Menu.kt

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import de.bixilon.minosoft.gui.rendering.gui.GUIRenderer
2222
import de.bixilon.minosoft.gui.rendering.gui.elements.Element
2323
import de.bixilon.minosoft.gui.rendering.gui.gui.AbstractLayout
2424
import de.bixilon.minosoft.gui.rendering.gui.gui.screen.Screen
25+
import de.bixilon.minosoft.gui.rendering.gui.input.MouseCapturing
2526
import de.bixilon.minosoft.gui.rendering.gui.input.mouse.MouseActions
2627
import de.bixilon.minosoft.gui.rendering.gui.input.mouse.MouseButtons
2728
import de.bixilon.minosoft.gui.rendering.gui.mesh.GUIVertexOptions
@@ -39,6 +40,8 @@ abstract class Menu(
3940

4041
override var activeElement: Element? = null
4142
override var activeDragElement: Element? = null
43+
private var capturingElement: MouseCapturing? = null
44+
private var capturingElementOffset: Vec2f = Vec2f.EMPTY
4245

4346
override fun forceSilentApply() {
4447
val elementWidth = maxOf(minOf(preferredElementWidth, size.x / 3), 0.0f)
@@ -84,6 +87,17 @@ abstract class Menu(
8487
}
8588

8689
override fun onMouseMove(position: Vec2f, absolute: Vec2f): Boolean {
90+
// Check if an element is capturing mouse
91+
val capturing = capturingElement
92+
if (capturing != null && capturing.isCapturingMouse) {
93+
val relativeX = position.x - capturingElementOffset.x
94+
capturing.onMouseMoveOutside(relativeX)
95+
return true
96+
}
97+
if (capturing != null && !capturing.isCapturingMouse) {
98+
capturingElement = null
99+
}
100+
87101
super<AbstractLayout>.onMouseMove(position, absolute)
88102
return true
89103
}
@@ -94,8 +108,26 @@ abstract class Menu(
94108
}
95109

96110
override fun onMouseAction(position: Vec2f, button: MouseButtons, action: MouseActions, count: Int): Boolean {
111+
val capturing = capturingElement
112+
if (capturing != null && capturing.isCapturingMouse) {
113+
val relativeX = position.x - capturingElementOffset.x
114+
if (capturing.onMouseActionOutside(relativeX, button, action)) {
115+
if (!capturing.isCapturingMouse) {
116+
capturingElement = null
117+
}
118+
return true
119+
}
120+
}
121+
97122
val (element, delta) = getAt(position) ?: return true
98123
element.onMouseAction(delta, button, action, count)
124+
125+
// Track elements that start capturing mouse
126+
if (element is MouseCapturing && element.isCapturingMouse) {
127+
capturingElement = element
128+
capturingElementOffset = Vec2f(position.x - delta.x, position.y - delta.y)
129+
}
130+
99131
return true
100132
}
101133

src/main/java/de/bixilon/minosoft/gui/rendering/gui/gui/screen/menu/options/CloudSettingsMenu.kt

Lines changed: 20 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -29,24 +29,20 @@ import de.bixilon.minosoft.gui.rendering.gui.gui.screen.menu.Menu
2929
class CloudSettingsMenu(guiRenderer: GUIRenderer) : Menu(guiRenderer, PREFERRED_WIDTH) {
3030
private val cloudProfile = guiRenderer.context.profile.sky.clouds
3131

32+
private val cloudsEnabledButton: ButtonElement
33+
private val cloudsFlatButton: ButtonElement
34+
private val cloudsMovementButton: ButtonElement
35+
private val maxDistanceSlider: SliderElement
36+
private val layersSlider: SliderElement
37+
3238
init {
3339
this += TextElement(guiRenderer, "menu.options.clouds.title".i18n(), background = null, properties = TextRenderProperties(HorizontalAlignments.CENTER, scale = 2.0f))
3440
this += SpacerElement(guiRenderer, Vec2f(0.0f, 10.0f))
3541

36-
lateinit var cloudsFlatButton: ButtonElement
37-
lateinit var cloudsMovementButton: ButtonElement
38-
39-
fun updateCloudsDisabledStates() {
40-
val cloudsDisabled = !cloudProfile.enabled
41-
cloudsFlatButton.disabled = cloudsDisabled
42-
cloudsMovementButton.disabled = cloudsDisabled
43-
}
44-
45-
lateinit var cloudsEnabledButton: ButtonElement
4642
cloudsEnabledButton = ButtonElement(guiRenderer, formatEnabled("menu.options.clouds.enabled", cloudProfile.enabled)) {
4743
cloudProfile.enabled = !cloudProfile.enabled
4844
cloudsEnabledButton.textElement.text = formatEnabled("menu.options.clouds.enabled", cloudProfile.enabled)
49-
updateCloudsDisabledStates()
45+
updateDisabledStates()
5046
}
5147
this += cloudsEnabledButton
5248

@@ -62,18 +58,28 @@ class CloudSettingsMenu(guiRenderer: GUIRenderer) : Menu(guiRenderer, PREFERRED_
6258
}
6359
this += cloudsMovementButton
6460

65-
this += SliderElement(guiRenderer, translate("menu.options.clouds.max_distance"), 0.0f, 200.0f, cloudProfile.maxDistance) {
61+
maxDistanceSlider = SliderElement(guiRenderer, translate("menu.options.clouds.max_distance"), 0.0f, 200.0f, cloudProfile.maxDistance) {
6662
cloudProfile.maxDistance = it
6763
}
64+
this += maxDistanceSlider
6865

69-
this += SliderElement(guiRenderer, translate("menu.options.clouds.layers"), 1.0f, 3.0f, cloudProfile.layers.toFloat()) {
66+
layersSlider = SliderElement(guiRenderer, translate("menu.options.clouds.layers"), 1.0f, 3.0f, cloudProfile.layers.toFloat()) {
7067
cloudProfile.layers = it.toInt()
7168
}
69+
this += layersSlider
7270

7371
this += SpacerElement(guiRenderer, Vec2f(0.0f, 10.0f))
7472
this += ButtonElement(guiRenderer, "menu.options.done".i18n()) { guiRenderer.gui.pop() }
7573

76-
updateCloudsDisabledStates()
74+
updateDisabledStates()
75+
}
76+
77+
private fun updateDisabledStates() {
78+
val cloudsDisabled = !cloudProfile.enabled
79+
cloudsFlatButton.disabled = cloudsDisabled
80+
cloudsMovementButton.disabled = cloudsDisabled
81+
maxDistanceSlider.disabled = cloudsDisabled
82+
layersSlider.disabled = cloudsDisabled
7783
}
7884

7985
companion object : GUIBuilder<LayoutedGUIElement<CloudSettingsMenu>> {

0 commit comments

Comments
 (0)