From e236585ef124069123616b2a78c7c1d410cf691e Mon Sep 17 00:00:00 2001 From: Moritz Zwerger Date: Thu, 23 Nov 2023 18:36:33 +0100 Subject: [PATCH] outsource javafx text component rendering --- src/main/java/de/bixilon/minosoft/Minosoft.kt | 10 +- .../minosoft/data/text/BaseComponent.kt | 15 +-- .../minosoft/data/text/ChatComponent.kt | 14 -- .../minosoft/data/text/EmptyComponent.kt | 5 +- .../minosoft/data/text/TextComponent.kt | 67 ---------- .../minosoft/gui/eros/util/JavaFXUtil.kt | 3 +- .../gui/eros/util/text/JavaFXTextRenderer.kt | 122 ++++++++++++++++++ 7 files changed, 135 insertions(+), 101 deletions(-) create mode 100644 src/main/java/de/bixilon/minosoft/gui/eros/util/text/JavaFXTextRenderer.kt diff --git a/src/main/java/de/bixilon/minosoft/Minosoft.kt b/src/main/java/de/bixilon/minosoft/Minosoft.kt index 49ca9bcfa7..1278fd8960 100644 --- a/src/main/java/de/bixilon/minosoft/Minosoft.kt +++ b/src/main/java/de/bixilon/minosoft/Minosoft.kt @@ -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) } } diff --git a/src/main/java/de/bixilon/minosoft/data/text/BaseComponent.kt b/src/main/java/de/bixilon/minosoft/data/text/BaseComponent.kt index 671b6516ec..b1fe830ea7 100644 --- a/src/main/java/de/bixilon/minosoft/data/text/BaseComponent.kt +++ b/src/main/java/de/bixilon/minosoft/data/text/BaseComponent.kt @@ -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 { val parts: MutableList = mutableListOf() constructor(parts: MutableList) { @@ -165,13 +163,6 @@ class BaseComponent : ChatComponent { return stringBuilder.toString() } - override fun getJavaFXText(nodes: ObservableList): ObservableList { - 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 { + return parts.iterator() + } } diff --git a/src/main/java/de/bixilon/minosoft/data/text/ChatComponent.kt b/src/main/java/de/bixilon/minosoft/data/text/ChatComponent.kt index 65d32894cf..55bfa8ecdb 100644 --- a/src/main/java/de/bixilon/minosoft/data/text/ChatComponent.kt +++ b/src/main/java/de/bixilon/minosoft/data/text/ChatComponent.kt @@ -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): ObservableList - - /** - * @return Returns a list of Nodes, drawable in JavaFX (TextFlow) - */ - val javaFXText: ObservableList - get() = getJavaFXText(FXCollections.observableArrayList()) - val textFlow: TextFlow get() { val textFlow = TextFlow() diff --git a/src/main/java/de/bixilon/minosoft/data/text/EmptyComponent.kt b/src/main/java/de/bixilon/minosoft/data/text/EmptyComponent.kt index b822e834bf..b3c9423ea7 100644 --- a/src/main/java/de/bixilon/minosoft/data/text/EmptyComponent.kt +++ b/src/main/java/de/bixilon/minosoft/data/text/EmptyComponent.kt @@ -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() - override fun getJavaFXText(nodes: ObservableList): ObservableList = nodes - override fun setFallbackColor(color: RGBColor) = this override fun getTextAt(pointer: Int): TextComponent = throw IllegalArgumentException() diff --git a/src/main/java/de/bixilon/minosoft/data/text/TextComponent.kt b/src/main/java/de/bixilon/minosoft/data/text/TextComponent.kt index 93b61aaf16..2f1c03f233 100644 --- a/src/main/java/de/bixilon/minosoft/data/text/TextComponent.kt +++ b/src/main/java/de/bixilon/minosoft/data/text/TextComponent.kt @@ -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): ObservableList { - 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() diff --git a/src/main/java/de/bixilon/minosoft/gui/eros/util/JavaFXUtil.kt b/src/main/java/de/bixilon/minosoft/gui/eros/util/JavaFXUtil.kt index c129ec3150..a46d9ec653 100644 --- a/src/main/java/de/bixilon/minosoft/gui/eros/util/JavaFXUtil.kt +++ b/src/main/java/de/bixilon/minosoft/gui/eros/util/JavaFXUtil.kt @@ -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? diff --git a/src/main/java/de/bixilon/minosoft/gui/eros/util/text/JavaFXTextRenderer.kt b/src/main/java/de/bixilon/minosoft/gui/eros/util/text/JavaFXTextRenderer.kt new file mode 100644 index 0000000000..c3819adcaf --- /dev/null +++ b/src/main/java/de/bixilon/minosoft/gui/eros/util/text/JavaFXTextRenderer.kt @@ -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 . + * + * 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 { + + fun render(nodes: MutableList, text: C) + + + object BaseComponentRenderer : JavaFXTextRenderer { + override fun render(nodes: MutableList, text: BaseComponent) { + for (part in text.parts) { + render(nodes, part) + } + } + } + + object TextComponentRenderer : JavaFXTextRenderer { + override fun render(nodes: MutableList, 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 { + + fun render(text: ChatComponent): MutableList { + val nodes: MutableList = mutableListOf() + render(nodes, text) + + return nodes + } + + override fun render(nodes: MutableList, 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" } + } + } +}