Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 6 additions & 4 deletions src/main/java/de/bixilon/minosoft/config/key/KeyBinding.kt
Original file line number Diff line number Diff line change
Expand Up @@ -14,23 +14,25 @@
package de.bixilon.minosoft.config.key

import com.fasterxml.jackson.annotation.JsonInclude
import de.bixilon.minosoft.util.KUtil.synchronizedDeepCopy
import com.fasterxml.jackson.databind.annotation.JsonDeserialize

class KeyBinding(
@field:JsonDeserialize(contentAs = LinkedHashSet::class)
val action: MutableMap<KeyActions, MutableSet<KeyCodes>>,
@JsonInclude(JsonInclude.Include.NON_DEFAULT) var ignoreConsumer: Boolean = false,
ignored: Boolean = true, // to prevent constructor overloading
) {
constructor(keyBinding: KeyBinding) : this(keyBinding.action.synchronizedDeepCopy())
constructor(keyBinding: KeyBinding) : this(keyBinding.action.copy(), keyBinding.ignoreConsumer)
constructor(action: Map<KeyActions, Set<KeyCodes>>, ignoreConsumer: Boolean = false) : this(action.copy(), ignoreConsumer)
constructor(vararg action: Pair<KeyActions, Set<KeyCodes>>, ignoreConsumer: Boolean = false) : this(mapOf(*action), ignoreConsumer)


companion object {
private fun Map<KeyActions, Set<KeyCodes>>.copy(): MutableMap<KeyActions, MutableSet<KeyCodes>> {
val next: MutableMap<KeyActions, MutableSet<KeyCodes>> = mutableMapOf()
val next: MutableMap<KeyActions, MutableSet<KeyCodes>> = linkedMapOf()
for ((action, codes) in this) {
next[action] = codes.toMutableSet()
// Use LinkedHashSet to save and display controls same order as entered by player after the restart.
next[action] = LinkedHashSet(codes)
}
return next
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/*
* Minosoft
* Copyright (C) 2020-2023 Moritz Zwerger
* Copyright (C) 2020-2026 Moritz Zwerger
*
* This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
*
Expand All @@ -20,7 +20,7 @@ import de.bixilon.minosoft.config.profile.profiles.Profile
open class EnumDelegate<T : Enum<*>>(
override val profile: Profile,
default: T,
values: ValuesEnum<T>,
val values: ValuesEnum<T>,
) : SimpleDelegate<T>(profile, default) {

override fun validate(value: T) = Unit
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/*
* Minosoft
* Copyright (C) 2020-2023 Moritz Zwerger
* Copyright (C) 2020-2026 Moritz Zwerger
*
* This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
*
Expand All @@ -27,7 +27,7 @@ object IntegratedLanguage {

fun load(name: String) {
Log.log(LogMessageType.LOADING, LogLevels.VERBOSE) { "Loading language files (${name})" }
val language = LanguageUtil.load(name, null, IntegratedAssets.DEFAULT, minosoft("language/"))
val language = LanguageUtil.load(name, null, IntegratedAssets.DEFAULT, minosoft("language/"), integrated = false)
LANGUAGE.translators[Namespaces.MINOSOFT] = language
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/*
* Minosoft
* Copyright (C) 2020-2025 Moritz Zwerger
* Copyright (C) 2020-2026 Moritz Zwerger
*
* This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
*
Expand Down Expand Up @@ -99,7 +99,7 @@ object LanguageUtil {
}


fun load(language: String, version: Version?, assets: AssetsManager, path: ResourceLocation = ResourceLocation.of("lang/")): Translator {
fun load(language: String, version: Version?, assets: AssetsManager, path: ResourceLocation = ResourceLocation.of("lang/"), integrated: Boolean = true): Translator {
val name = language.lowercase()
val json = version != null && version.jsonLanguage

Expand All @@ -114,6 +114,10 @@ object LanguageUtil {
}
loadLanguage(FALLBACK_LANGUAGE, assets, json, path)?.let { translators += it }

if (integrated) {
translators += IntegratedLanguage.LANGUAGE
}

if (translators.size == 1) {
return translators.first()
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ package de.bixilon.minosoft.gui.rendering.gui.elements.input.button

import de.bixilon.kmath.vec.vec2.f.Vec2f
import de.bixilon.minosoft.config.key.KeyCodes
import de.bixilon.minosoft.data.registries.identified.Namespaces.minecraft
import de.bixilon.minosoft.gui.rendering.gui.GUIRenderer
import de.bixilon.minosoft.gui.rendering.gui.atlas.AtlasElement
import de.bixilon.minosoft.gui.rendering.gui.elements.Element
Expand All @@ -28,16 +27,18 @@ import de.bixilon.minosoft.gui.rendering.gui.elements.text.TextElement
import de.bixilon.minosoft.gui.rendering.gui.input.mouse.MouseActions
import de.bixilon.minosoft.gui.rendering.gui.input.mouse.MouseButtons
import de.bixilon.minosoft.gui.rendering.gui.mesh.GUIVertexOptions
import de.bixilon.minosoft.gui.rendering.gui.mesh.GUIVertexOptions.Companion.copy
import de.bixilon.minosoft.gui.rendering.gui.mesh.consumer.GuiVertexConsumer
import de.bixilon.minosoft.gui.rendering.system.window.CursorShapes
import de.bixilon.minosoft.gui.rendering.system.window.KeyChangeTypes
import de.bixilon.minosoft.data.registries.identified.Namespaces.minecraft

abstract class AbstractButtonElement(
guiRenderer: GUIRenderer,
text: Any,
disabled: Boolean = false,
) : Element(guiRenderer) {
protected val textElement = TextElement(guiRenderer, text, background = null, parent = this)
val textElement = TextElement(guiRenderer, text, background = null, parent = this)
protected abstract val disabledAtlas: AtlasElement?
protected abstract val normalAtlas: AtlasElement?
protected abstract val hoveredAtlas: AtlasElement?
Expand Down Expand Up @@ -80,7 +81,7 @@ abstract class AbstractButtonElement(
if (_disabled == value) {
return
}
_disabled = disabled
_disabled = value
forceApply()
}

Expand Down Expand Up @@ -113,8 +114,10 @@ abstract class AbstractButtonElement(
background.size = size
val textSize = textElement.size

background.render(offset, consumer, options)
textElement.render(offset + Vec2f(HorizontalAlignments.CENTER.getOffset(size.x, textSize.x), VerticalAlignments.CENTER.getOffset(size.y, textSize.y)), consumer, options)
val renderOptions = if (disabled) options.copy(alpha = 0.4f) else options

background.render(offset, consumer, renderOptions)
textElement.render(offset + Vec2f(HorizontalAlignments.CENTER.getOffset(size.x, textSize.x), VerticalAlignments.CENTER.getOffset(size.y, textSize.y)), consumer, renderOptions)
}

override fun forceSilentApply() {
Expand Down Expand Up @@ -188,7 +191,7 @@ abstract class AbstractButtonElement(
}

private companion object {
val CLICK_SOUND = minecraft("ui.button.click")
val CLICK_SOUND = minecraft("ui.button.click") // This still doesnt play sound for some reason...
const val TEXT_PADDING = 4
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,229 @@
/*
* Minosoft
* Copyright (C) 2020-2025 Moritz Zwerger
*
* This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with this program. If not, see <https://www.gnu.org/licenses/>.
*
* This software is not affiliated with Mojang AB, the original developer of Minecraft.
*/

package de.bixilon.minosoft.gui.rendering.gui.elements.input.slider

import de.bixilon.kmath.vec.vec2.f.Vec2f
import de.bixilon.minosoft.data.registries.identified.Namespaces.minecraft
import de.bixilon.minosoft.gui.rendering.gui.GUIRenderer
import de.bixilon.minosoft.gui.rendering.gui.elements.Element
import de.bixilon.minosoft.gui.rendering.gui.elements.primitive.AtlasImageElement
import de.bixilon.minosoft.gui.rendering.gui.elements.text.TextElement
import de.bixilon.minosoft.gui.rendering.gui.input.MouseCapturing
import de.bixilon.minosoft.gui.rendering.gui.mesh.GUIVertexOptions.Companion.copy
import de.bixilon.minosoft.gui.rendering.gui.input.mouse.MouseActions
import de.bixilon.minosoft.gui.rendering.gui.input.mouse.MouseButtons
import de.bixilon.minosoft.gui.rendering.gui.mesh.GUIVertexOptions
import de.bixilon.minosoft.gui.rendering.gui.mesh.consumer.GuiVertexConsumer
import de.bixilon.minosoft.gui.rendering.system.window.CursorShapes
import kotlin.math.roundToInt

class SliderElement(
guiRenderer: GUIRenderer,
private val label: String,
private val min: Float,
private val max: Float,
initialValue: Float,
private val onChange: (Float) -> Unit
) : Element(guiRenderer), MouseCapturing {
private val buttonAtlas = guiRenderer.atlas[BUTTON_ATLAS]

var textElement: TextElement
private var isDragging = false
private var isHovered = false
private var isHandleHovered = false

var disabled: Boolean = false
set(value) {
if (field == value) return
field = value
cacheUpToDate = false
}

override val isCapturingMouse: Boolean get() = isDragging

var value: Float = initialValue.coerceIn(min, max)
set(value) {
val clamped = value.coerceIn(min, max)
if (field != clamped) {
field = clamped
updateText()
onChange(clamped)
cacheUpToDate = false
}
}

init {
textElement = TextElement(guiRenderer, getDisplayText(), background = null, parent = this)
updateText()
size = Vec2f(textElement.size.x + TEXT_PADDING * 2, SLIDER_HEIGHT)
}

private fun getDisplayText(): String {
return "$label: ${value.roundToInt()}"
}

private fun updateText() {
textElement.text = getDisplayText()
textElement.silentApply()
if (size.x == 0.0f) {
size = Vec2f(textElement.size.x + TEXT_PADDING * 2, SLIDER_HEIGHT)
}
}

override fun forceSilentApply() {
textElement.silentApply()
cacheUpToDate = false
}

override fun forceRender(offset: Vec2f, consumer: GuiVertexConsumer, options: GUIVertexOptions?) {
val size = size
val renderOptions = if (disabled) options.copy(alpha = 0.4f) else options

val trackTexture = buttonAtlas?.get("disabled") ?: guiRenderer.context.textures.whiteTexture
val trackBackground = AtlasImageElement(guiRenderer, trackTexture)
trackBackground.size = size
trackBackground.render(offset, consumer, renderOptions)

val normalizedValue = (value - min) / (max - min)
val handleWidth = HANDLE_WIDTH
val trackWidth = size.x - handleWidth
val handleX = trackWidth * normalizedValue

val handleTexture = if (disabled) {
buttonAtlas?.get("disabled")
} else if (isHandleHovered || isDragging) {
buttonAtlas?.get("hovered")
} else {
buttonAtlas?.get("normal")
} ?: guiRenderer.context.textures.whiteTexture

val slider = AtlasImageElement(guiRenderer, handleTexture)
slider.size = Vec2f(handleWidth, SLIDER_HEIGHT)
slider.render(offset + Vec2f(handleX, 0.0f), consumer, renderOptions)

val textSize = textElement.size
val textX = (size.x - textSize.x) / 2
val textY = (size.y - textSize.y) / 2
textElement.render(offset + Vec2f(textX, textY), consumer, renderOptions)
}

override fun onMouseAction(position: Vec2f, button: MouseButtons, action: MouseActions, count: Int): Boolean {
if (disabled) {
return true
}
if (button != MouseButtons.LEFT) {
return true
}

when (action) {
MouseActions.PRESS -> {
isDragging = true
updateValueFromPosition(position.x)
cacheUpToDate = false
}
MouseActions.RELEASE -> {
isDragging = false
context.window.resetCursor()
cacheUpToDate = false
}
}

return true
}

override fun onMouseMove(position: Vec2f, absolute: Vec2f): Boolean {
if (isDragging) {
updateValueFromPosition(position.x)
}

if (!isDragging) {
val normalizedValue = (value - min) / (max - min)
val handleWidth = HANDLE_WIDTH
val trackWidth = size.x - handleWidth
val handleX = trackWidth * normalizedValue

val wasHandleHovered = isHandleHovered
isHandleHovered = position.x >= handleX && position.x < handleX + handleWidth

if (wasHandleHovered != isHandleHovered) {
cacheUpToDate = false
}
}

return true
}

override fun onMouseEnter(position: Vec2f, absolute: Vec2f): Boolean {
isHovered = true
context.window.cursorShape = CursorShapes.HAND

val normalizedValue = (value - min) / (max - min)
val handleWidth = HANDLE_WIDTH
val trackWidth = size.x - handleWidth
val handleX = trackWidth * normalizedValue

isHandleHovered = position.x >= handleX && position.x < handleX + handleWidth
cacheUpToDate = false
return true
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the return value means that the event is consumed. Is it when not dragging?

}

override fun onMouseLeave(): Boolean {
isHovered = false
isHandleHovered = false
if (!isDragging) {
context.window.resetCursor()
}
cacheUpToDate = false
return super.onMouseLeave()
}

override fun onMouseActionOutside(relativeX: Float, button: MouseButtons, action: MouseActions): Boolean {
if (!isDragging) return false

if (button == MouseButtons.LEFT && action == MouseActions.RELEASE) {
isDragging = false
context.window.resetCursor()
cacheUpToDate = false
return true
}
return false
}

override fun onMouseMoveOutside(relativeX: Float): Boolean {
if (!isDragging) return false
updateValueFromPosition(relativeX)
return true
}

private fun updateValueFromPosition(x: Float) {
val handleWidth = HANDLE_WIDTH
val trackWidth = size.x - handleWidth

if (trackWidth <= 0) {
value = min
return
}

val normalizedX = (x - handleWidth / 2) / trackWidth
value = min + normalizedX * (max - min)
}

companion object {
val BUTTON_ATLAS = minecraft("elements/button")
private const val TEXT_PADDING = 4.0f
private const val SLIDER_HEIGHT = 20.0f
private const val HANDLE_WIDTH = 8.0f
}
}

Loading
Loading