diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 70e881e1f..fa162c885 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -8,16 +8,10 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v1 - - name: Setup Java JDK - uses: actions/setup-java@v1.3.0 - with: - # The Java version to make available on the path. Takes a whole or semver Java version, or 1.x syntax (e.g. 1.8 => Java 8.x) - java-version: 11 - # The package type (jre, jdk, jdk+fx) - java-package: jdk+fx - - name: Gradle Command - uses: eskatos/gradle-command-action@v1 - with: - # Gradle command line arguments, see gradle --help - arguments: build -x mirai-core:jvmTest + - uses: actions/checkout@v1 + - name: setup-android + uses: msfjarvis/setup-android@0.2 + with: + # Gradle tasks to run - If you want to run ./gradlew assemble, specify assemble here. + gradleTasks: build -x mirai-core:jvmTest + diff --git a/mirai-console-graphical/build.gradle.kts b/mirai-console-graphical/build.gradle.kts index e52642d61..29e821737 100644 --- a/mirai-console-graphical/build.gradle.kts +++ b/mirai-console-graphical/build.gradle.kts @@ -1,9 +1,15 @@ plugins { id("kotlinx-serialization") + id("org.openjfx.javafxplugin") version "0.0.8" id("kotlin") id("java") } +javafx { + version = "11" + modules = listOf("javafx.controls") + //mainClassName = "Application" +} apply(plugin = "com.github.johnrengelman.shadow") @@ -29,6 +35,7 @@ dependencies { api(project(":mirai-console")) runtimeOnly(files("../mirai-core-qqandroid/build/classes/kotlin/jvm/main")) api(group = "no.tornado", name = "tornadofx", version = "1.7.19") + api(group = "com.jfoenix", name = "jfoenix", version = "9.0.8") api("org.bouncycastle:bcprov-jdk15on:1.64") // classpath is not set correctly by IDE } diff --git a/mirai-console-graphical/src/main/kotlin/net/mamoe/mirai/console/graphical/util/JFoenixAdaptor.kt b/mirai-console-graphical/src/main/kotlin/net/mamoe/mirai/console/graphical/util/JFoenixAdaptor.kt new file mode 100644 index 000000000..94663e510 --- /dev/null +++ b/mirai-console-graphical/src/main/kotlin/net/mamoe/mirai/console/graphical/util/JFoenixAdaptor.kt @@ -0,0 +1,44 @@ +package net.mamoe.mirai.console.graphical.util + +import com.jfoenix.controls.* +import javafx.beans.value.ObservableValue +import javafx.collections.ObservableList +import javafx.event.EventTarget +import javafx.scene.Node +import javafx.scene.control.* +import tornadofx.SortedFilteredList +import tornadofx.attachTo +import tornadofx.bind + +internal fun EventTarget.jfxTabPane(op: TabPane.() -> Unit = {}) = JFXTabPane().attachTo(this, op) + +internal fun EventTarget.jfxButton(text: String = "", graphic: Node? = null, op: Button.() -> Unit = {}) = + JFXButton(text).attachTo(this, op) { + if (graphic != null) it.graphic = graphic + } + +fun EventTarget.jfxTextfield(value: String? = null, op: TextField.() -> Unit = {}) = JFXTextField().attachTo(this, op) { + if (value != null) it.text = value +} + +fun EventTarget.jfxTextfield(property: ObservableValue, op: TextField.() -> Unit = {}) = jfxTextfield().apply { + bind(property) + op(this) +} + +fun EventTarget.jfxPasswordfield(value: String? = null, op: TextField.() -> Unit = {}) = JFXPasswordField().attachTo(this, op) { + if (value != null) it.text = value +} + +fun EventTarget.jfxPasswordfield(property: ObservableValue, op: TextField.() -> Unit = {}) = jfxPasswordfield().apply { + bind(property) + op(this) +} + +internal fun EventTarget.jfxListView(values: ObservableList? = null, op: ListView.() -> Unit = {}) = + JFXListView().attachTo(this, op) { + if (values != null) { + if (values is SortedFilteredList) values.bindTo(it) + else it.items = values + } + } diff --git a/mirai-console-graphical/src/main/kotlin/net/mamoe/mirai/console/graphical/view/LoginFragment.kt b/mirai-console-graphical/src/main/kotlin/net/mamoe/mirai/console/graphical/view/LoginFragment.kt index 1290b7808..c0f1b460c 100644 --- a/mirai-console-graphical/src/main/kotlin/net/mamoe/mirai/console/graphical/view/LoginFragment.kt +++ b/mirai-console-graphical/src/main/kotlin/net/mamoe/mirai/console/graphical/view/LoginFragment.kt @@ -1,27 +1,31 @@ package net.mamoe.mirai.console.graphical.view +import com.jfoenix.controls.JFXTextField import javafx.beans.property.SimpleStringProperty import net.mamoe.mirai.console.graphical.controller.MiraiGraphicalUIController +import net.mamoe.mirai.console.graphical.util.jfxButton +import net.mamoe.mirai.console.graphical.util.jfxPasswordfield +import net.mamoe.mirai.console.graphical.util.jfxTextfield import tornadofx.* class LoginFragment : Fragment() { private val controller = find(FX.defaultScope) - private val qq = SimpleStringProperty() - private val psd = SimpleStringProperty() + private val qq = SimpleStringProperty("0") + private val psd = SimpleStringProperty("") override val root = form { fieldset("登录") { field("QQ") { - textfield(qq) + jfxTextfield(qq) } field("密码") { - passwordfield(psd) - } - button("登录").action { - controller.login(qq.value, psd.value) - close() + jfxPasswordfield(psd) } } + jfxButton("登录").action { + controller.login(qq.value, psd.value) + close() + } } } \ No newline at end of file diff --git a/mirai-console-graphical/src/main/kotlin/net/mamoe/mirai/console/graphical/view/PrimaryView.kt b/mirai-console-graphical/src/main/kotlin/net/mamoe/mirai/console/graphical/view/PrimaryView.kt index 1900e25cc..58036ac6e 100644 --- a/mirai-console-graphical/src/main/kotlin/net/mamoe/mirai/console/graphical/view/PrimaryView.kt +++ b/mirai-console-graphical/src/main/kotlin/net/mamoe/mirai/console/graphical/view/PrimaryView.kt @@ -1,10 +1,20 @@ package net.mamoe.mirai.console.graphical.view +import com.jfoenix.controls.JFXListCell +import javafx.geometry.Insets +import javafx.geometry.Pos +import javafx.scene.control.Tab import javafx.scene.control.TabPane -import javafx.stage.Modality -import net.mamoe.mirai.Bot +import javafx.scene.image.Image +import javafx.scene.paint.Color +import javafx.scene.text.FontWeight import net.mamoe.mirai.console.graphical.controller.MiraiGraphicalUIController +import net.mamoe.mirai.console.graphical.model.BotModel +import net.mamoe.mirai.console.graphical.util.jfxButton +import net.mamoe.mirai.console.graphical.util.jfxListView +import net.mamoe.mirai.console.graphical.util.jfxTabPane import tornadofx.* +import java.io.FileInputStream class PrimaryView : View() { @@ -12,45 +22,87 @@ class PrimaryView : View() { override val root = borderpane { - top = menubar { - menu("机器人") { - item("登录").action { - find().openModal( - modality = Modality.APPLICATION_MODAL, - resizable = false - ) + prefWidth = 1000.0 + prefHeight = 650.0 + + left = vbox { + + imageview(Image(PrimaryView::class.java.classLoader.getResourceAsStream("logo.png"))) + + // bot list + jfxListView(controller.botList) { + fitToParentSize() + + setCellFactory { + object : JFXListCell() { + var tab: Tab? = null + + init { + onDoubleClick { + if (tab == null) { + (center as TabPane).tab(item.uin.toString()) { + listview(item.logHistory) + onDoubleClick { close() } + tab = this + } + } else { + (center as TabPane).tabs.add(tab) + } + tab?.select() + } + } + + override fun updateItem(item: BotModel?, empty: Boolean) { + super.updateItem(item, empty) + if (item != null && !empty) { + graphic = null + text = item.uin.toString() + } else { + graphic = null + text = "" + } + } + } + } + } + + hbox { + padding = Insets(10.0) + spacing = 10.0 + alignment = Pos.CENTER + + jfxButton("L").action { + find().openModal() + } + jfxButton("P") + jfxButton("S") + + + style { backgroundColor += c("00BCD4") } + children.style(true) { + backgroundColor += c("00BCD4") + fontSize = 15.px + fontWeight = FontWeight.BOLD + textFill = Color.WHITE + borderRadius += box(25.px) + backgroundRadius += box(25.px) } } } - left = listview(controller.botList) { - fitToParentHeight() + center = jfxTabPane { + tab("Main") { + listview(controller.mainLog) { - cellFormat { - - graphic = vbox { - label(it.uin.toString()) -// label(stringBinding(it.botProperty) { if (value != null) value.nick else "登陆中" }) - } - - onDoubleClick { - (center as TabPane).tab(it.uin.toString()) { - listview(it.logHistory) - - isClosable = true - select() + fitToParentSize() + cellFormat { + graphic = label(it) { + maxWidthProperty().bind(this@listview.widthProperty()) + isWrapText = true + } } } } } - - center = tabpane { - tab("Main") { - listview(controller.mainLog) - - isClosable = false - } - } } - -} \ No newline at end of file +} diff --git a/mirai-console-graphical/src/main/resources/logo.png b/mirai-console-graphical/src/main/resources/logo.png new file mode 100644 index 000000000..7be6c6871 Binary files /dev/null and b/mirai-console-graphical/src/main/resources/logo.png differ diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/event/MessageSubscribers.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/event/MessageSubscribers.kt index 1c32ad94b..5cb5489c0 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/event/MessageSubscribers.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/event/MessageSubscribers.kt @@ -28,6 +28,8 @@ import kotlin.contracts.contract /** * 订阅来自所有 [Bot] 的所有联系人的消息事件. 联系人可以是任意群或任意好友或临时会话. + * + * @see CoroutineScope.incoming */ @UseExperimental(ExperimentalContracts::class) @MessageDsl @@ -49,6 +51,8 @@ inline fun CoroutineScope.subscribeMessages(crossinline listeners: MessageSu /** * 订阅来自所有 [Bot] 的所有群消息事件 + * + * @see CoroutineScope.incoming */ @UseExperimental(ExperimentalContracts::class) @MessageDsl @@ -65,6 +69,8 @@ inline fun CoroutineScope.subscribeGroupMessages(crossinline listeners: Mess /** * 订阅来自所有 [Bot] 的所有好友消息事件 + * + * @see CoroutineScope.incoming */ @UseExperimental(ExperimentalContracts::class) @MessageDsl @@ -81,6 +87,8 @@ inline fun CoroutineScope.subscribeFriendMessages(crossinline listeners: Mes /** * 订阅来自这个 [Bot] 的所有联系人的消息事件. 联系人可以是任意群或任意好友或临时会话. + * + * @see CoroutineScope.incoming */ @UseExperimental(ExperimentalContracts::class) @MessageDsl @@ -97,6 +105,8 @@ inline fun Bot.subscribeMessages(crossinline listeners: MessageSubscribersBu /** * 订阅来自这个 [Bot] 的所有群消息事件 + * + * @see CoroutineScope.incoming */ @UseExperimental(ExperimentalContracts::class) @MessageDsl @@ -113,6 +123,8 @@ inline fun Bot.subscribeGroupMessages(crossinline listeners: MessageSubscrib /** * 订阅来自这个 [Bot] 的所有好友消息事件. + * + * @see CoroutineScope.incoming */ @UseExperimental(ExperimentalContracts::class) @MessageDsl @@ -129,9 +141,15 @@ inline fun Bot.subscribeFriendMessages(crossinline listeners: MessageSubscri /** * 返回一个指定事件的接收通道 + * + * @param capacity 同 [Channel] 的参数, 参见 [Channel.Factory] 中的常量. + * + * @see subscribeFriendMessages + * @see subscribeMessages + * @see subscribeGroupMessages */ -inline fun Bot.incoming(): ReceiveChannel { - return Channel(8).apply { +inline fun CoroutineScope.incoming(capacity: Int = Channel.RENDEZVOUS): ReceiveChannel { + return Channel(capacity).apply { subscribeAlways { send(this) } diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/event/internal/InternalEventListeners.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/event/internal/InternalEventListeners.kt index c72941469..e439a0974 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/event/internal/InternalEventListeners.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/event/internal/InternalEventListeners.kt @@ -90,6 +90,15 @@ internal object EventListenerManager { private val lock = atomic(false) + private fun setLockValue(value: Boolean) { + lock.value = value + } + + @Suppress("BooleanLiteralArgument") + private fun trySetLockTrue(): Boolean { + return lock.compareAndSet(false, true) + } + @Suppress("UNCHECKED_CAST", "BooleanLiteralArgument") internal tailrec fun get(clazz: KClass): EventListeners { registries.forEach { @@ -97,10 +106,10 @@ internal object EventListenerManager { return it.listeners as EventListeners } } - if (lock.compareAndSet(false, true)) { + if (trySetLockTrue()) { val registry = Registry(clazz, EventListeners()) registries.addLast(registry) - lock.value = false + setLockValue(false) return registry.listeners as EventListeners } return get(clazz) diff --git a/mirai-demos/mirai-demo-1/src/main/java/demo/subscribe/SubscribeSamples.kt b/mirai-demos/mirai-demo-1/src/main/java/demo/subscribe/SubscribeSamples.kt index 79c921519..88931f882 100644 --- a/mirai-demos/mirai-demo-1/src/main/java/demo/subscribe/SubscribeSamples.kt +++ b/mirai-demos/mirai-demo-1/src/main/java/demo/subscribe/SubscribeSamples.kt @@ -28,8 +28,10 @@ import net.mamoe.mirai.message.sendAsImageTo import net.mamoe.mirai.qqandroid.Bot import net.mamoe.mirai.qqandroid.QQAndroid import net.mamoe.mirai.utils.FileBasedDeviceInfo +import net.mamoe.mirai.utils.MiraiInternalAPI import java.io.File +@MiraiInternalAPI private fun readTestAccount(): BotAccount? { val file = File("testAccount.txt") if (!file.exists() || !file.canRead()) { @@ -59,7 +61,7 @@ suspend fun main() { bot.messageDSL() directlySubscribe(bot) - bot.network.awaitDisconnection()//等到直到断开连接 + bot.join()//等到直到断开连接 } /** diff --git a/mirai-demos/mirai-demo-gentleman/src/main/kotlin/demo/gentleman/Main.kt b/mirai-demos/mirai-demo-gentleman/src/main/kotlin/demo/gentleman/Main.kt index bd15503f9..5efe33cd8 100644 --- a/mirai-demos/mirai-demo-gentleman/src/main/kotlin/demo/gentleman/Main.kt +++ b/mirai-demos/mirai-demo-gentleman/src/main/kotlin/demo/gentleman/Main.kt @@ -176,7 +176,7 @@ suspend fun main() { } - bot.network.awaitDisconnection()//等到直到断开连接 + bot.join()//等到直到断开连接 } private fun newTestTempFile(filename: String = "${UUID.randomUUID()}", suffix: String = ".tmp"): File = diff --git a/settings.gradle b/settings.gradle index 9dc004651..cd3ee4be4 100644 --- a/settings.gradle +++ b/settings.gradle @@ -44,7 +44,6 @@ include(':mirai-core-qqandroid') include(':mirai-japt') include(':mirai-console') -include(':mirai-console-graphical') include(':mirai-console-terminal') //include(':mirai-api') include(':mirai-api-http') @@ -54,24 +53,23 @@ include(':mirai-demos:mirai-demo-java') include(':mirai-plugins') include(':mirai-plugins:image-sender') -def javaVersion = System.getProperty("java.version") -def versionPos = javaVersion.indexOf(".") -if (versionPos==-1) versionPos = javaVersion.indexOf("-") -if (versionPos==-1){ - println("jdk version unknown") -}else{ - def javaVersionNum = javaVersion.substring(0, versionPos).toInteger() - if (javaVersionNum >= 11) { - println("jdk版本为 "+ javaVersionNum) - //include(':mirai-debug') - } else { - println("当前使用的 JDK 版本为 ${System.getProperty("java.version")}, 最低需要 JDK 11 才能引入模块 `:mirai-debug`") +try{ + def javaVersion = System.getProperty("java.version") + def versionPos = javaVersion.indexOf(".") + if (versionPos==-1) versionPos = javaVersion.indexOf("-") + if (versionPos==-1){ + println("jdk version unknown") + }else{ + def javaVersionNum = javaVersion.substring(0, versionPos).toInteger() + if (javaVersionNum >= 11) { + println("jdk版本为 "+ javaVersionNum) + include(':mirai-console-graphical') + } else { + println("当前使用的 JDK 版本为 ${System.getProperty("java.version")}, 最低需要 JDK 11 才能引入模块 `:mirai-debug`") + } } +}catch(Exception ignored){ + } -project(':mirai-demos:mirai-demo-1').projectDir = file('mirai-demos/mirai-demo-1') -project(':mirai-demos:mirai-demo-gentleman').projectDir = file('mirai-demos/mirai-demo-gentleman') -project(':mirai-demos:mirai-demo-java').projectDir = file('mirai-demos/mirai-demo-java') -project(':mirai-plugins:image-sender').projectDir = file('mirai-plugins/image-sender') - enableFeaturePreview('GRADLE_METADATA') \ No newline at end of file