outsource javafx text component rendering

This commit is contained in:
Moritz Zwerger 2023-11-23 18:36:33 +01:00
parent 89e4254ddd
commit e236585ef1
No known key found for this signature in database
GPG Key ID: 5CAD791931B09AC4
7 changed files with 135 additions and 101 deletions

View File

@ -90,13 +90,14 @@ object Minosoft {
val taskWorker = TaskWorker(errorHandler = { _, error -> error.printStackTrace(); error.crash() }, forcePool = true)
MinosoftBoot.register(taskWorker)
taskWorker += WorkerTask(identifier = BootTasks.LANGUAGE_FILES, dependencies = arrayOf(BootTasks.PROFILES), executor = this::loadLanguageFiles)
if (!RunConfiguration.DISABLE_EROS) {
javafx(taskWorker)
}
if (RunConfiguration.DISABLE_EROS && !RunConfiguration.DISABLE_RENDERING) {
// eros is disabled, but rendering not, force initialize the desktop, otherwise eros will do so
// eros is disabled, but rendering not, force initialize the desktop, because eros won't
DefaultThreadPool += { SystemUtil.api = DesktopAPI() }
}
@ -160,9 +161,8 @@ object Minosoft {
}
private fun checkMacOS() {
if (RunConfiguration.X_START_ON_FIRST_THREAD_SET && (!RunConfiguration.DISABLE_RENDERING || !RunConfiguration.DISABLE_EROS)) {
Log.log(LogMessageType.GENERAL, LogLevels.WARN) { "You are using macOS. To use rendering you must not set the jvm argument §9-XstartOnFirstThread§r. Please remove it!" }
ShutdownManager.shutdown(reason = AbstractShutdownReason.CRASH)
}
if (!RunConfiguration.X_START_ON_FIRST_THREAD_SET || !(!RunConfiguration.DISABLE_RENDERING || !RunConfiguration.DISABLE_EROS)) return
Log.log(LogMessageType.GENERAL, LogLevels.WARN) { "You are using macOS. To use rendering you must not set the jvm argument §9-XstartOnFirstThread§r. Please remove it!" }
ShutdownManager.shutdown(reason = AbstractShutdownReason.CRASH)
}
}

View File

@ -29,10 +29,8 @@ import de.bixilon.minosoft.protocol.protocol.ProtocolDefinition
import de.bixilon.minosoft.util.KUtil.format
import de.bixilon.minosoft.util.KUtil.toResourceLocation
import de.bixilon.minosoft.util.nbt.tag.NBTUtil.get
import javafx.collections.ObservableList
import javafx.scene.Node
class BaseComponent : ChatComponent {
class BaseComponent : ChatComponent, Iterable<ChatComponent> {
val parts: MutableList<ChatComponent> = mutableListOf()
constructor(parts: MutableList<ChatComponent>) {
@ -165,13 +163,6 @@ class BaseComponent : ChatComponent {
return stringBuilder.toString()
}
override fun getJavaFXText(nodes: ObservableList<Node>): ObservableList<Node> {
for (part in parts) {
part.getJavaFXText(nodes)
}
return nodes
}
override fun obfuscate(): BaseComponent {
for (part in parts) part.obfuscate(); return this
}
@ -284,4 +275,8 @@ class BaseComponent : ChatComponent {
return parts.toTypedArray()
}
override fun iterator(): Iterator<ChatComponent> {
return parts.iterator()
}
}

View File

@ -21,9 +21,6 @@ import de.bixilon.minosoft.data.registries.identified.ResourceLocation
import de.bixilon.minosoft.data.text.formatting.color.RGBColor
import de.bixilon.minosoft.gui.eros.util.JavaFXUtil.text
import de.bixilon.minosoft.util.json.Jackson
import javafx.collections.FXCollections
import javafx.collections.ObservableList
import javafx.scene.Node
import javafx.scene.text.TextFlow
/**
@ -48,17 +45,6 @@ interface ChatComponent {
fun getJson(): Any
/**
* @return Returns a list of Nodes, drawable in JavaFX (TextFlow)
*/
fun getJavaFXText(nodes: ObservableList<Node>): ObservableList<Node>
/**
* @return Returns a list of Nodes, drawable in JavaFX (TextFlow)
*/
val javaFXText: ObservableList<Node>
get() = getJavaFXText(FXCollections.observableArrayList())
val textFlow: TextFlow
get() {
val textFlow = TextFlow()

View File

@ -14,8 +14,7 @@
package de.bixilon.minosoft.data.text
import de.bixilon.minosoft.data.text.formatting.color.RGBColor
import javafx.collections.ObservableList
import javafx.scene.Node
object EmptyComponent : ChatComponent {
override val ansi: String get() = ""
override val legacy: String get() = ""
@ -23,8 +22,6 @@ object EmptyComponent : ChatComponent {
override fun getJson(): Any = emptyList<Any>()
override fun getJavaFXText(nodes: ObservableList<Node>): ObservableList<Node> = nodes
override fun setFallbackColor(color: RGBColor) = this
override fun getTextAt(pointer: Int): TextComponent = throw IllegalArgumentException()

View File

@ -14,7 +14,6 @@ package de.bixilon.minosoft.data.text
import de.bixilon.kutil.enums.BitEnumSet
import de.bixilon.kutil.json.MutableJsonObject
import de.bixilon.minosoft.config.profile.profiles.eros.ErosProfileManager
import de.bixilon.minosoft.data.registries.identified.ResourceLocation
import de.bixilon.minosoft.data.text.events.click.ClickEvent
import de.bixilon.minosoft.data.text.events.hover.HoverEvent
@ -23,15 +22,6 @@ import de.bixilon.minosoft.data.text.formatting.TextStyle
import de.bixilon.minosoft.data.text.formatting.color.ChatColors
import de.bixilon.minosoft.data.text.formatting.color.RGBColor
import de.bixilon.minosoft.protocol.protocol.ProtocolDefinition
import javafx.animation.Animation
import javafx.animation.KeyFrame
import javafx.animation.Timeline
import javafx.collections.ObservableList
import javafx.scene.Node
import javafx.scene.paint.Color
import javafx.scene.text.Text
import javafx.util.Duration
import java.util.concurrent.atomic.AtomicInteger
open class TextComponent(
@ -118,63 +108,6 @@ open class TextComponent(
return builder.toString()
}
override fun getJavaFXText(nodes: ObservableList<Node>): ObservableList<Node> {
val text = Text(this.message)
val color = this.color
if (color == null) {
text.styleClass += "text-default-color"
} else {
if (ErosProfileManager.selected.text.colored) {
text.fill = Color.rgb(color.red, color.green, color.blue)
}
}
if (FormattingCodes.OBFUSCATED in formatting) {
// ToDo: This is just slow
val obfuscatedTimeline = if (ErosProfileManager.selected.text.obfuscated) {
val index = AtomicInteger()
Timeline(
KeyFrame(Duration.millis(50.0), {
val chars = text.text.toCharArray()
for (i in chars.indices) {
chars[i] = ProtocolDefinition.OBFUSCATED_CHARS[index.getAndIncrement() % ProtocolDefinition.OBFUSCATED_CHARS.size]
}
text.text = String(chars)
}),
)
} else {
Timeline(
KeyFrame(Duration.millis(500.0), {
text.isVisible = false
}),
KeyFrame(Duration.millis(1000.0), {
text.isVisible = true
}),
)
}
obfuscatedTimeline.cycleCount = Animation.INDEFINITE
obfuscatedTimeline.play()
text.styleClass.add("obfuscated")
}
if (FormattingCodes.BOLD in formatting) {
text.style += "-fx-font-weight: bold;"
}
if (FormattingCodes.STRIKETHROUGH in formatting) {
text.style += "-fx-strikethrough: true;"
}
if (FormattingCodes.UNDERLINED in formatting) {
text.style += "-fx-underline: true;"
}
if (FormattingCodes.ITALIC in formatting) {
text.style += "-fx-font-style: italic;"
}
nodes.add(text)
clickEvent?.applyJavaFX(text)
hoverEvent?.applyJavaFX(text)
return nodes
}
override fun getJson(): Any {
if (message.isEmpty()) {
return emptyMap<String, Any>()

View File

@ -28,6 +28,7 @@ import de.bixilon.minosoft.data.registries.identified.ResourceLocation
import de.bixilon.minosoft.gui.eros.controller.EmbeddedJavaFXController
import de.bixilon.minosoft.gui.eros.controller.JavaFXController
import de.bixilon.minosoft.gui.eros.controller.JavaFXWindowController
import de.bixilon.minosoft.gui.eros.util.text.JavaFXTextRenderer
import de.bixilon.minosoft.util.KUtil.toResourceLocation
import de.bixilon.minosoft.util.crash.freeze.FreezeDumpUtil
import de.bixilon.minosoft.util.delegate.JavaFXDelegate.observeFX
@ -148,7 +149,7 @@ object JavaFXUtil {
var TextFlow.text: Any?
get() = TODO("Can not get the text of a TextFlow (yet)")
set(value) {
this.children.setAll(IntegratedLanguage.LANGUAGE.translate(value).javaFXText)
this.children.setAll(JavaFXTextRenderer.render(IntegratedLanguage.LANGUAGE.translate(value)))
}
var TextField.placeholder: Any?

View File

@ -0,0 +1,122 @@
/*
* Minosoft
* Copyright (C) 2020-2023 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.eros.util.text
import de.bixilon.minosoft.config.profile.profiles.eros.ErosProfileManager
import de.bixilon.minosoft.data.text.BaseComponent
import de.bixilon.minosoft.data.text.ChatComponent
import de.bixilon.minosoft.data.text.EmptyComponent
import de.bixilon.minosoft.data.text.TextComponent
import de.bixilon.minosoft.data.text.formatting.FormattingCodes
import de.bixilon.minosoft.protocol.protocol.ProtocolDefinition
import de.bixilon.minosoft.util.logging.Log
import de.bixilon.minosoft.util.logging.LogLevels
import de.bixilon.minosoft.util.logging.LogMessageType
import javafx.animation.Animation
import javafx.animation.KeyFrame
import javafx.animation.Timeline
import javafx.scene.Node
import javafx.scene.paint.Color
import javafx.scene.text.Text
import javafx.util.Duration
import java.util.concurrent.atomic.AtomicInteger
interface JavaFXTextRenderer<C> {
fun render(nodes: MutableList<Node>, text: C)
object BaseComponentRenderer : JavaFXTextRenderer<BaseComponent> {
override fun render(nodes: MutableList<Node>, text: BaseComponent) {
for (part in text.parts) {
render(nodes, part)
}
}
}
object TextComponentRenderer : JavaFXTextRenderer<TextComponent> {
override fun render(nodes: MutableList<Node>, text: TextComponent) {
val node = Text(text.message)
val color = text.color
if (color == null) {
node.styleClass += "text-default-color"
} else {
if (ErosProfileManager.selected.text.colored) {
node.fill = Color.rgb(color.red, color.green, color.blue)
}
}
if (FormattingCodes.OBFUSCATED in text.formatting) {
// ToDo: This is just slow
val obfuscatedTimeline = if (ErosProfileManager.selected.text.obfuscated) {
val index = AtomicInteger()
Timeline(
KeyFrame(Duration.millis(50.0), {
val chars = node.text.toCharArray()
for (i in chars.indices) {
chars[i] = ProtocolDefinition.OBFUSCATED_CHARS[index.getAndIncrement() % ProtocolDefinition.OBFUSCATED_CHARS.size]
}
node.text = String(chars)
}),
)
} else {
Timeline(
KeyFrame(Duration.millis(500.0), {
node.isVisible = false
}),
KeyFrame(Duration.millis(1000.0), {
node.isVisible = true
}),
)
}
obfuscatedTimeline.cycleCount = Animation.INDEFINITE
obfuscatedTimeline.play()
node.styleClass.add("obfuscated")
}
if (FormattingCodes.BOLD in text.formatting) {
node.style += "-fx-font-weight: bold;"
}
if (FormattingCodes.STRIKETHROUGH in text.formatting) {
node.style += "-fx-strikethrough: true;"
}
if (FormattingCodes.UNDERLINED in text.formatting) {
node.style += "-fx-underline: true;"
}
if (FormattingCodes.ITALIC in text.formatting) {
node.style += "-fx-font-style: italic;"
}
nodes.add(node)
text.clickEvent?.applyJavaFX(node)
text.hoverEvent?.applyJavaFX(node)
}
}
companion object : JavaFXTextRenderer<ChatComponent> {
fun render(text: ChatComponent): MutableList<Node> {
val nodes: MutableList<Node> = mutableListOf()
render(nodes, text)
return nodes
}
override fun render(nodes: MutableList<Node>, text: ChatComponent) = when (text) {
is EmptyComponent -> Unit
is BaseComponent -> BaseComponentRenderer.render(nodes, text)
is TextComponent -> TextComponentRenderer.render(nodes, text)
else -> Log.log(LogMessageType.OTHER, LogLevels.WARN) { "Can not render $text" }
}
}
}