Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Compose POC #51

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
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
45 changes: 45 additions & 0 deletions libui-compose/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinNativeTarget

plugins {
kotlin("multiplatform")
id("org.jetbrains.compose") version "1.2.1"
}

val os = org.gradle.internal.os.OperatingSystem.current()!!
val isRunningInIde: Boolean = System.getProperty("idea.active") == "true"

kotlin {
if (os.isWindows) mingwX64("windows")
if (os.isLinux) linuxX64("linux")
if (os.isMacOsX) macosX64("macosx")

sourceSets {
commonMain {
dependencies {
implementation(kotlin("stdlib-common"))
implementation(compose.runtime)
}
}
commonTest {
kotlin.srcDir("src/unitTest/kotlin")
dependencies {
implementation(kotlin("test-common"))
implementation(kotlin("test-annotations-common"))
}
}
}

targets.withType<KotlinNativeTarget> {
sourceSets["${targetName}Main"].apply {
kotlin.srcDir("src/nativeMain/kotlin")
dependencies {
api(project(":libui"))
}
}

binaries {
executable(listOf(RELEASE)) {
}
}
}
}
62 changes: 62 additions & 0 deletions libui-compose/src/nativeMain/kotlin/Main.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import androidx.compose.runtime.*
import kotlinx.coroutines.*
import libui.compose.*
import libui.uiQuit

fun main() {
println("HELLOOOOOO!!!")

runLibUI {
val state = remember { WindowState(SizeInt(100, 100)) }

Window(
onCloseRequest = {
println("CLOSE REQUEST!!!")
uiQuit()
},
state = state,
title = "LibUI and Compose!",
content = {
var num by remember { mutableStateOf(1) }
val isOn = remember { mutableStateOf(true) }

VBox {
Label("I did something here! $num")
Label("Look at this thing go ${num + 20}")

Checkbox("Enable disappearing label", isOn)
if (!isOn.value || num % 4 == 0) {
Label("I like to have stuff in here")
}
HorizontalSeparator()

Button("Click Me!", onClick = { println("You clicked me!") })

VerticalSeparator()

ProgressBar()
ProgressBar(value = num % 100)

val color = remember { mutableStateOf(Color(0xFFFFFF)) }
ColorButton(color)

val simpleText = remember { mutableStateOf("Type here...") }
TextField(simpleText)
PasswordField(simpleText)

HorizontalSeparator()

val slide = remember { mutableStateOf(5) }
Slider(slide, 1, 20 + num / 10)
}

LaunchedEffect(Unit) {
while (isActive) {
delay(1000)
num += 1
}
}
}
)
}
}
64 changes: 64 additions & 0 deletions libui-compose/src/nativeMain/kotlin/libui/compose/Box.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
@file:Suppress("FunctionName")

package libui.compose

import androidx.compose.runtime.Applier
import androidx.compose.runtime.Composable
import androidx.compose.runtime.ComposeNode
import cnames.structs.uiBox
import kotlinx.cinterop.CPointer
import libui.*

@Composable
fun VBox(
padded: Boolean = true,
enabled: Boolean = true,
visible: Boolean = true,
content: @Composable () -> Unit
) {
Box(ctor = { uiNewVerticalBox()!! }, padded, enabled, visible, content)
}

@Composable
fun HBox(
padded: Boolean = true,
enabled: Boolean = true,
visible: Boolean = true,
content: @Composable () -> Unit
) {
Box(ctor = { uiNewHorizontalBox()!! }, padded, enabled, visible, content)
}

@Composable
private fun Box(
ctor: () -> CPointer<uiBox>,
padded: Boolean,
enabled: Boolean,
visible: Boolean,
content: @Composable () -> Unit
) {
val control = rememberControl { ctor() }

handleChildren(content) { BoxApplier(control.ptr) }

ComposeNode<CPointer<uiBox>, Applier<CPointer<uiControl>>>(
factory = { control.ptr },
update = {
setCommon(enabled, visible)
set(padded) { uiBoxSetPadded(this, if (it) 1 else 0) }
}
)
}

class BoxApplier(
private val box: CPointer<uiBox>,
) : AppendDeleteApplier() {
override fun deleteItem(index: Int) {
uiBoxDelete(box, index)
}

override fun appendItem(instance: CPointer<uiControl>?) {
val isStretchy = false
uiBoxAppend(box, instance, if (isStretchy) 1 else 0)
}
}
103 changes: 103 additions & 0 deletions libui-compose/src/nativeMain/kotlin/libui/compose/Entry.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
@file:Suppress("FunctionName")

package libui.compose

import cnames.structs.uiWindow
import androidx.compose.runtime.*
import androidx.compose.runtime.snapshots.Snapshot
import kotlinx.cinterop.*
import kotlinx.coroutines.*
import kotlinx.coroutines.flow.*
import libui.*
import kotlin.coroutines.CoroutineContext

fun runLibUI(content: @Composable WindowScope.() -> Unit) = withLibUI {
runBlocking {
@OptIn(ExperimentalStdlibApi::class)
val uiDispatcher = LibUiDispatcher(coroutineContext[CoroutineDispatcher.Key]!!)

val clock = BroadcastFrameClock()

val scope = CoroutineScope(clock + uiDispatcher)

scope.launch {
while (isActive) {
clock.sendFrame(0L) // Frame time value is not used by Compose runtime.
delay(50)
}
}

Snapshot.globalWrites()
.conflate()
.onEach { Snapshot.sendApplyNotifications() }
.launchIn(scope)

val recomposer = Recomposer(scope.coroutineContext)
// Will be cancelled when recomposerJob cancels
scope.launch { recomposer.runRecomposeAndApplyChanges() }

composeLibUI(recomposer, content) {
uiMain()
uiDispatcher.close()
}

recomposer.close()
recomposer.join()

scope.cancel()
}
}

private inline fun composeLibUI(
parent: CompositionContext,
noinline content: @Composable WindowScope.() -> Unit,
block: () -> Unit
) {
val applier = MutableListApplier<CPointer<uiWindow>>(mutableListOf())
val composition = Composition(applier, parent)
composition.setContent { WindowScope().content() }

block()

// Free libui controls
composition.dispose()
}

private class LibUiDispatcher(private val backup: CoroutineDispatcher) : CloseableCoroutineDispatcher() {
private var isClosed: Boolean = false

override fun close() {
// There's a race condition between close() and dispatch() to fix.
isClosed = true

// val onShouldQuit = { println("QUIT!") }
// val lol = StableRef.create(onShouldQuit)
// try {
// uiOnShouldQuit(
// staticCFunction { senderData ->
// 1
// },
// lol.asCPointer()
// )
// } finally {
// lol.dispose()
// }
}

override fun dispatch(context: CoroutineContext, block: Runnable) {
if (!isClosed) {
val stableRef = StableRef.create(block)
uiQueueMain(
staticCFunction { ptr ->
val ref = ptr!!.asStableRef<Runnable>()
val runnable = ref.get()
ref.dispose()
runnable.run()
},
stableRef.asCPointer()
)
} else {
backup.dispatch(context, block)
}
}
}
42 changes: 42 additions & 0 deletions libui-compose/src/nativeMain/kotlin/libui/compose/Form.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package libui.compose

import androidx.compose.runtime.Applier
import androidx.compose.runtime.Composable
import androidx.compose.runtime.ComposeNode
import cnames.structs.uiForm
import kotlinx.cinterop.CPointer
import libui.*


@Composable
fun Form(
padded: Boolean = true,
enabled: Boolean = true,
visible: Boolean = true,
content: @Composable () -> Unit
) {
val control = rememberControl { uiNewForm()!! }

handleChildren(content) { FormApplier(control.ptr) }

ComposeNode<CPointer<uiForm>, Applier<CPointer<uiControl>>>(
factory = { control.ptr },
update = {
setCommon(enabled, visible)
set(padded) { uiFormSetPadded(this, if (it) 1 else 0) }
}
)
}


class FormApplier(private val form: CPointer<uiForm>) : AppendDeleteApplier() {
override fun appendItem(instance: CPointer<uiControl>?) {
val label = ""
val isStretchy = false
uiFormAppend(form, label, instance, if (isStretchy) 1 else 0)
}

override fun deleteItem(index: Int) {
uiFormDelete(form, index)
}
}
59 changes: 59 additions & 0 deletions libui-compose/src/nativeMain/kotlin/libui/compose/Layouts.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
@file:Suppress("FunctionName")

package libui.compose

import androidx.compose.runtime.*
import cnames.structs.uiGroup
//import cnames.structs.uiGrid
import kotlinx.cinterop.CPointer
import libui.*

@Composable
fun Group(
title: String,
margined: Boolean = false,
enabled: Boolean = true,
visible: Boolean = true,
content: @Composable () -> Unit
) {
val control = rememberControl { uiNewGroup(title)!! }

handleChildren(content) {
object : SingletonApplier<CPointer<uiControl>>() {
override fun setItem(item: CPointer<uiControl>?) {
uiGroupSetChild(control.ptr, item)
}
}
}

ComposeNode<CPointer<uiGroup>, Applier<CPointer<uiControl>>>(
factory = { control.ptr },
update = {
setCommon(enabled, visible)
update(title) { uiGroupSetTitle(this, it) }
set(margined) { uiGroupSetMargined(this, if (it) 1 else 0) }
}
)
}

//@Composable
//fun Grid(
// padded: Boolean = true,
// enabled: Boolean = true,
// visible: Boolean = true,
// content: @Composable () -> Unit
//) {
// val control = rememberControl { uiNewGrid()!! }
//
//// handleChildren(content) {
//// GridApplier(control.ptr)
//// }
//
// ComposeNode<CPointer<uiGrid>, Applier<CPointer<uiControl>>>(
// factory = { control.ptr },
// update = {
// setCommon(enabled, visible)
// set(padded) { uiGridSetPadded(this, if (it) 1 else 0) }
// }
// )
//}
Loading