Move all-things to Compose

This commit is contained in:
Alexander Nozik 2024-05-11 17:56:28 +03:00
parent 381da970bf
commit 24b6856f15
7 changed files with 146 additions and 161 deletions

View File

@ -3,7 +3,6 @@ import space.kscience.gradle.useSPCTeam
plugins {
id("space.kscience.gradle.project")
alias(libs.plugins.versions)
}
allprojects {

View File

@ -35,7 +35,7 @@ public class VirtualLimitSwitch(
public val lockedState: DeviceState<Boolean>,
) : DeviceBySpec<LimitSwitch>(LimitSwitch, context), LimitSwitch {
init {
override suspend fun onStart() {
lockedState.valueFlow.onEach {
propertyChanged(LimitSwitch.locked, it)
}.launchIn(this)

View File

@ -1,7 +1,6 @@
plugins {
kotlin("jvm")
id("org.openjfx.javafxplugin") version "0.0.13"
application
alias(spclibs.plugins.compose)
}
@ -20,28 +19,46 @@ dependencies {
implementation(projects.controlsOpcua)
implementation(spclibs.ktor.client.cio)
implementation(libs.tornadofx)
implementation(libs.plotlykt.server)
// implementation("com.github.Ricky12Awesome:json-schema-serialization:0.6.6")
implementation(compose.runtime)
implementation(compose.desktop.currentOs)
implementation(compose.material3)
// implementation("org.pushing-pixels:aurora-window:1.3.0")
// implementation("org.pushing-pixels:aurora-component:1.3.0")
// implementation("org.pushing-pixels:aurora-theming:1.3.0")
implementation(spclibs.logback.classic)
}
kotlin{
jvmToolchain(11)
}
tasks.withType<org.jetbrains.kotlin.gradle.tasks.KotlinCompile>().configureEach {
kotlinOptions {
freeCompilerArgs = freeCompilerArgs + listOf("-Xjvm-default=all", "-Xopt-in=kotlin.RequiresOptIn")
jvmToolchain(17)
compilerOptions {
freeCompilerArgs.addAll("-Xjvm-default=all", "-Xopt-in=kotlin.RequiresOptIn")
}
}
javafx {
version = "17"
modules("javafx.controls")
}
application {
mainClass.set("space.kscience.controls.demo.DemoControllerViewKt")
compose{
desktop{
application{
mainClass = "space.kscience.controls.demo.DemoControllerViewKt"
}
}
}
//
//
//tasks.withType<org.jetbrains.kotlin.gradle.tasks.KotlinCompile>().configureEach {
// kotlinOptions {
// freeCompilerArgs = freeCompilerArgs + listOf("-Xjvm-default=all", "-Xopt-in=kotlin.RequiresOptIn")
// }
//}
//
//javafx {
// version = "17"
// modules("javafx.controls")
//}
//
//application {
// mainClass.set("space.kscience.controls.demo.DemoControllerViewKt")
//}

View File

@ -1,14 +1,19 @@
package space.kscience.controls.demo
import androidx.compose.foundation.layout.*
import androidx.compose.material.*
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import androidx.compose.ui.window.Window
import androidx.compose.ui.window.application
import androidx.compose.ui.window.rememberWindowState
import io.ktor.server.engine.ApplicationEngine
import javafx.scene.Parent
import javafx.scene.control.Slider
import javafx.scene.layout.Priority
import javafx.stage.Stage
import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import kotlinx.serialization.json.Json
import org.eclipse.milo.opcua.sdk.server.OpcUaServer
import org.eclipse.milo.opcua.stack.core.types.builtin.LocalizedText
@ -17,9 +22,6 @@ import space.kscience.controls.api.GetDescriptionMessage
import space.kscience.controls.api.PropertyChangedMessage
import space.kscience.controls.client.launchMagixService
import space.kscience.controls.client.magixFormat
import space.kscience.controls.demo.DemoDevice.Companion.cosScale
import space.kscience.controls.demo.DemoDevice.Companion.sinScale
import space.kscience.controls.demo.DemoDevice.Companion.timeScale
import space.kscience.controls.manager.DeviceManager
import space.kscience.controls.manager.install
import space.kscience.controls.opcua.server.OpcUaServer
@ -35,16 +37,15 @@ import space.kscience.magix.rsocket.rSocketWithWebSockets
import space.kscience.magix.server.RSocketMagixFlowPlugin
import space.kscience.magix.server.startMagixServer
import space.kscince.magix.zmq.ZmqMagixFlowPlugin
import tornadofx.*
import java.awt.Desktop
import java.net.URI
class DemoController : Controller(), ContextAware {
class DemoController : ContextAware {
var device: DemoDevice? = null
var magixServer: ApplicationEngine? = null
var visualizer: ApplicationEngine? = null
var opcUaServer: OpcUaServer = OpcUaServer {
val opcUaServer: OpcUaServer = OpcUaServer {
setApplicationName(LocalizedText.english("space.kscience.controls.opcua"))
endpoint {
@ -60,39 +61,37 @@ class DemoController : Controller(), ContextAware {
private val deviceManager = context.request(DeviceManager)
fun init() {
context.launch {
device = deviceManager.install("demo", DemoDevice)
//starting magix event loop
magixServer = startMagixServer(
RSocketMagixFlowPlugin(), //TCP rsocket support
ZmqMagixFlowPlugin() //ZMQ support
)
//Launch a device client and connect it to the server
val deviceEndpoint = MagixEndpoint.rSocketWithTcp("localhost")
deviceManager.launchMagixService(deviceEndpoint)
//connect visualization to a magix endpoint
val visualEndpoint = MagixEndpoint.rSocketWithWebSockets("localhost")
visualizer = startDemoDeviceServer(visualEndpoint)
fun start(): Job = context.launch {
device = deviceManager.install("demo", DemoDevice)
//starting magix event loop
magixServer = startMagixServer(
RSocketMagixFlowPlugin(), //TCP rsocket support
ZmqMagixFlowPlugin() //ZMQ support
)
//Launch a device client and connect it to the server
val deviceEndpoint = MagixEndpoint.rSocketWithTcp("localhost")
deviceManager.launchMagixService(deviceEndpoint)
//connect visualization to a magix endpoint
val visualEndpoint = MagixEndpoint.rSocketWithWebSockets("localhost")
visualizer = startDemoDeviceServer(visualEndpoint)
//serve devices as OPC-UA namespace
opcUaServer.startup()
opcUaServer.serveDevices(deviceManager)
//serve devices as OPC-UA namespace
opcUaServer.startup()
opcUaServer.serveDevices(deviceManager)
val listenerEndpoint = MagixEndpoint.rSocketWithWebSockets("localhost")
listenerEndpoint.subscribe(DeviceManager.magixFormat).onEach { (_, deviceMessage)->
// print all messages that are not property change message
if(deviceMessage !is PropertyChangedMessage){
println(">> ${Json.encodeToString(DeviceMessage.serializer(), deviceMessage)}")
}
}.launchIn(this)
listenerEndpoint.send(DeviceManager.magixFormat, GetDescriptionMessage(), "listener", "controls-kt")
val listenerEndpoint = MagixEndpoint.rSocketWithWebSockets("localhost")
listenerEndpoint.subscribe(DeviceManager.magixFormat).onEach { (_, deviceMessage) ->
// print all messages that are not property change message
if (deviceMessage !is PropertyChangedMessage) {
println(">> ${Json.encodeToString(DeviceMessage.serializer(), deviceMessage)}")
}
}.launchIn(this)
listenerEndpoint.send(DeviceManager.magixFormat, GetDescriptionMessage(), "listener", "controls-kt")
}
}
suspend fun shutdown() {
fun shutdown(): Job = context.launch {
logger.info { "Shutting down..." }
opcUaServer.shutdown()
logger.info { "OpcUa server stopped" }
@ -102,92 +101,82 @@ class DemoController : Controller(), ContextAware {
logger.info { "Magix server stopped" }
device?.stop()
logger.info { "Device server stopped" }
context.close()
}
}
@Composable
fun DemoControls(controller: DemoController) {
var timeScale by remember { mutableStateOf(5000f) }
var xScale by remember { mutableStateOf(1f) }
var yScale by remember { mutableStateOf(1f) }
Surface(Modifier.padding(5.dp)) {
Column {
Row(Modifier.fillMaxWidth()) {
Text("Time Scale", modifier = Modifier.align(Alignment.CenterVertically).width(100.dp))
TextField(String.format("%.2f", timeScale),{}, enabled = false, modifier = Modifier.align(Alignment.CenterVertically).width(100.dp))
Slider(timeScale, onValueChange = { timeScale = it }, steps = 20, valueRange = 1000f..5000f)
}
Row(Modifier.fillMaxWidth()) {
Text("X scale", modifier = Modifier.align(Alignment.CenterVertically).width(100.dp))
TextField(String.format("%.2f", xScale),{}, enabled = false, modifier = Modifier.align(Alignment.CenterVertically).width(100.dp))
Slider(xScale, onValueChange = { xScale = it }, steps = 20, valueRange = 0.1f..2.0f)
}
Row(Modifier.fillMaxWidth()) {
Text("Y scale", modifier = Modifier.align(Alignment.CenterVertically).width(100.dp))
TextField(String.format("%.2f", yScale),{}, enabled = false, modifier = Modifier.align(Alignment.CenterVertically).width(100.dp))
Slider(yScale, onValueChange = { yScale = it }, steps = 20, valueRange = 0.1f..2.0f)
}
Row(Modifier.fillMaxWidth()) {
Button(
onClick = {
controller.device?.run {
launch {
write(DemoDevice.timeScale, timeScale.toDouble())
write(DemoDevice.sinScale, xScale.toDouble())
write(DemoDevice.cosScale, yScale.toDouble())
}
}
},
Modifier.fillMaxWidth()
) {
Text("Submit")
}
}
Row(Modifier.fillMaxWidth()) {
Button(
onClick = {
controller.visualizer?.run {
val host = "localhost"//environment.connectors.first().host
val port = environment.connectors.first().port
val uri = URI("http", null, host, port, "/", null, null)
Desktop.getDesktop().browse(uri)
}
},
Modifier.fillMaxWidth()
) {
Text("Show plots")
}
}
}
}
}
class DemoControllerView : View(title = " Demo controller remote") {
private val controller: DemoController by inject()
private var timeScaleSlider: Slider by singleAssign()
private var xScaleSlider: Slider by singleAssign()
private var yScaleSlider: Slider by singleAssign()
fun main() = application {
val controller = remember { DemoController().apply { start() } }
override val root: Parent = vbox {
hbox {
label("Time scale")
pane {
hgrow = Priority.ALWAYS
}
timeScaleSlider = slider(1000..10000, 5000) {
isShowTickLabels = true
isShowTickMarks = true
}
}
hbox {
label("X scale")
pane {
hgrow = Priority.ALWAYS
}
xScaleSlider = slider(0.1..2.0, 1.0) {
isShowTickLabels = true
isShowTickMarks = true
}
}
hbox {
label("Y scale")
pane {
hgrow = Priority.ALWAYS
}
yScaleSlider = slider(0.1..2.0, 1.0) {
isShowTickLabels = true
isShowTickMarks = true
}
}
button("Submit") {
useMaxWidth = true
action {
controller.device?.run {
launch {
write(timeScale, timeScaleSlider.value)
write(sinScale, xScaleSlider.value)
write(cosScale, yScaleSlider.value)
}
}
}
}
button("Show plots") {
useMaxWidth = true
action {
controller.visualizer?.run {
val host = "localhost"//environment.connectors.first().host
val port = environment.connectors.first().port
val uri = URI("http", null, host, port, "/", null, null)
Desktop.getDesktop().browse(uri)
}
}
}
}
}
class DemoControllerApp : App(DemoControllerView::class) {
private val controller: DemoController by inject()
override fun start(stage: Stage) {
super.start(stage)
controller.init()
}
override fun stop() {
runBlocking {
Window(
title = "All things control",
onCloseRequest = {
controller.shutdown()
exitApplication()
},
state = rememberWindowState(width = 400.dp, height = 300.dp)
) {
MaterialTheme {
DemoControls(controller)
}
super.stop()
}
}
fun main() {
launch<DemoControllerApp>()
}

View File

@ -1,10 +0,0 @@
package space.kscience.controls.demo
//import com.github.ricky12awesome.jss.encodeToSchema
//import com.github.ricky12awesome.jss.globalJson
//import space.kscience.controls.api.DeviceMessage
//fun main() {
// val schema = globalJson.encodeToSchema(DeviceMessage.serializer(), generateDefinitions = false)
// println(schema)
//}

View File

@ -1,19 +1,11 @@
plugins {
id("space.kscience.gradle.jvm")
application
id("org.openjfx.javafxplugin")
alias(spclibs.plugins.compose)
}
//TODO to be moved to a separate project
javafx {
version = "17"
modules = listOf("javafx.controls")
}
application{
mainClass.set("ru.mipt.npm.devices.pimotionmaster.PiMotionMasterAppKt")
}
//application{
// mainClass.set("ru.mipt.npm.devices.pimotionmaster.PiMotionMasterAppKt")
//}
kotlin{
explicitApi = null
@ -25,5 +17,4 @@ val dataforgeVersion: String by extra
dependencies {
implementation(project(":controls-ports-ktor"))
implementation(projects.controlsMagix)
implementation(libs.tornadofx)
}

View File

@ -18,7 +18,6 @@ pluginManagement {
id("space.kscience.gradle.mpp") version toolsVersion
id("space.kscience.gradle.jvm") version toolsVersion
id("space.kscience.gradle.js") version toolsVersion
id("org.openjfx.javafxplugin") version "0.0.13"
}
}