>>.() -> Unit = {
+ put(layout.container.asdHandle, setOf(RectangleProperty.KEY, BoundsProperty.KEY))
+ put(layout.component.asdHandle, setOf(IntrinsicDimensionsProperty.KEY))
+ }
+
+ override fun compute(layout: SingletonLayout, handle: LayoutHandle) = compute(
+ handle.getUnit(layout.component.asdHandle).getProperty(IntrinsicDimensionsProperty.KEY)!!
+ )
+
+ abstract fun compute(dim: IntrinsicDimensionsProperty): Dimension2D
+
+ /**
+ * Scale both dimensions by the same scaling
+ * @param scale `> 0`
+ */
+ data class Scale(val scale: Double) : Scaled() {
+ override fun compute(dim: IntrinsicDimensionsProperty) =
+ Dimension2D(dim.width.toDouble() * scale, dim.height.toDouble() * scale)
+ }
+
+ class Compute private constructor(private val x: Value, private val y: Value) : Scaled() {
+ private object MathEnvImpl : MathEnv
+
+ constructor(x: MathEnv.() -> Value, y: MathEnv.() -> Value) : this(x(MathEnvImpl), y(MathEnvImpl))
+
+ sealed interface Value {
+ fun compute(dim: IntrinsicDimensionsProperty): Double
+
+ operator fun plus(that: Value) = Operator.Plus(this, that)
+ operator fun minus(that: Value) = Operator.Minus(this, that)
+ operator fun times(that: Value) = Operator.Times(this, that)
+ operator fun div(that: Value) = Operator.Div(this, that)
+ }
+
+ sealed class Operator private constructor() : Value {
+ data class Plus(val a: Value, val b: Value) : Operator() {
+ override fun compute(dim: IntrinsicDimensionsProperty) = a.compute(dim) + b.compute(dim)
+ }
+ data class Minus(val a: Value, val b: Value) : Operator() {
+ override fun compute(dim: IntrinsicDimensionsProperty) = a.compute(dim) - b.compute(dim)
+ }
+ data class Times(val a: Value, val b: Value) : Operator() {
+ override fun compute(dim: IntrinsicDimensionsProperty) = a.compute(dim) * b.compute(dim)
+ }
+ data class Div(val a: Value, val b: Value) : Operator() {
+ override fun compute(dim: IntrinsicDimensionsProperty) = a.compute(dim) / b.compute(dim)
+ }
+ }
+
+ sealed class Param private constructor() : Value {
+ data class Num(val value: Double) : Param() {
+ override fun compute(dim: IntrinsicDimensionsProperty) = value
+ }
+ data object DimX : Param() {
+ override fun compute(dim: IntrinsicDimensionsProperty) = dim.width.toDouble()
+ }
+ data object DimY : Param() {
+ override fun compute(dim: IntrinsicDimensionsProperty) = dim.height.toDouble()
+ }
+ }
+
+ sealed interface MathEnv {
+ val dimX: Param get() = Param.DimX
+ val dimY: Param get() = Param.DimY
+ fun num(value: Double) = Param.Num(value)
+ }
+
+ override fun compute(dim: IntrinsicDimensionsProperty) = Dimension2D(x.compute(dim), y.compute(dim))
+ }
+ }
+
+ /**
+ * Range within `[0,1]`
+ */
+ data class AlignmentConfig(val x: Double, val y: Double) {
+ companion object {
+ val DEFAULT = AlignmentConfig(0.5, 0.5)
+ fun withX(x: Double) = AlignmentConfig(x, 0.5)
+ fun withY(y: Double) = AlignmentConfig(y, 0.5)
+ }
+ }
+ }
+
+ fun update(component: Component) {
+ operate {
+ this@SingletonLayout.component = component
+ }
+ }
+
+ interface ConfigEnv {
+ var config: Config
+ }
+
+ fun update(operation: ConfigEnv.() -> Unit) {
+ operate {
+ operation(object : ConfigEnv {
+ override var config: Config by this@SingletonLayout::config
+ })
+ }
+ }
+
+ override fun layOut(handle: LayoutHandle) =
+ sequenceOf(LayoutComputationGroup({}, { config.layOut(this@SingletonLayout) }))
+}
diff --git a/src/kernel/client/kotlin/net/terramodulus/mui/gui/agim/impl/SizedPane.kt b/src/kernel/client/kotlin/net/terramodulus/mui/gui/agim/impl/SizedPane.kt
new file mode 100644
index 00000000..8c731449
--- /dev/null
+++ b/src/kernel/client/kotlin/net/terramodulus/mui/gui/agim/impl/SizedPane.kt
@@ -0,0 +1,84 @@
+/*
+ * SPDX-FileCopyrightText: 2026 TerraModulus Team and Contributors
+ * SPDX-License-Identifier: LGPL-3.0-only
+ */
+
+package net.terramodulus.mui.gui.agim.impl
+
+import net.terramodulus.mui.gui.agim.AbstractPane
+import net.terramodulus.mui.gui.agim.AgimoPropertyMap
+import net.terramodulus.mui.gui.agim.Component
+import net.terramodulus.mui.gui.agim.Layout
+import net.terramodulus.mui.gui.agim.LayoutComputationGroup
+import net.terramodulus.mui.gui.agim.LayoutComputationUnit
+import net.terramodulus.mui.gui.agim.LayoutHandle
+import net.terramodulus.mui.gui.asd.AsdHandle
+import net.terramodulus.mui.gui.gfx.RenderSystem
+
+/**
+ * Provide intrinsic information for this container and its [component].
+ */
+class SizedPane(asdHandle: AsdHandle, component: Component, private var config: Config) : AbstractPane(asdHandle) {
+ private val _layout = SizedLayout(component)
+ override val layout: Layout get() = _layout
+
+ private inner class SizedLayout(var component: Component) : Layout(this@SizedPane) {
+ override val components = componentsSequence(::component)
+
+ override fun layOut(handle: LayoutHandle) = sequenceOf(LayoutComputationGroup({}, {
+ setOf(LayoutComputationUnit({}, {
+ put(component.asdHandle, setOf(IntrinsicRatioProperty.KEY, IntrinsicDimensionsProperty.KEY))
+ put(container.asdHandle, setOf(IntrinsicRatioProperty.KEY, IntrinsicDimensionsProperty.KEY))
+ }, {
+ val dim = IntrinsicDimensionsProperty(config.width, config.height)
+ val ratio = dim.computeRatio()
+ mapOf(
+ component.asdHandle to AgimoPropertyMap().apply {
+ putProperty(IntrinsicRatioProperty.KEY, ratio)
+ putProperty(IntrinsicDimensionsProperty.KEY, dim)
+ },
+ container.asdHandle to AgimoPropertyMap().apply {
+ putProperty(IntrinsicRatioProperty.KEY, ratio)
+ putProperty(IntrinsicDimensionsProperty.KEY, dim)
+ }
+ )
+ }), LayoutComputationUnit({ // Forwarding Rect to component
+ put(container.asdHandle, setOf(RectangleProperty.KEY, BoundsProperty.KEY))
+ }, {
+ put(component.asdHandle, setOf(BoundsProperty.KEY))
+ }, {
+ mapOf(component.asdHandle to AgimoPropertyMap().apply {
+ val prop = getUnit(container.asdHandle)
+ putProperty(BoundsProperty.KEY, BoundsProperty(
+ prop.getProperty(RectangleProperty.KEY)?.value
+ ?: prop.getProperty(BoundsProperty.KEY)!!.value)
+ )
+ })
+ }))
+ }))
+ }
+
+ data class Config(val width: UInt, val height: UInt)
+
+ fun update(component: Component) {
+ _layout.operate {
+ _layout.component = component
+ }
+ }
+
+ interface ConfigEnv {
+ var config: Config
+ }
+
+ fun update(operation: ConfigEnv.() -> Unit) {
+ _layout.operate {
+ operation(object : ConfigEnv {
+ override var config: Config by this@SizedPane::config
+ })
+ }
+ }
+
+ override fun render(renderSystem: RenderSystem) {
+ layout.render(renderSystem)
+ }
+}
diff --git a/src/kernel/client/kotlin/net/terramodulus/mui/gui/agim/impl/SliderComponent.kt b/src/kernel/client/kotlin/net/terramodulus/mui/gui/agim/impl/SliderComponent.kt
new file mode 100644
index 00000000..bc867d03
--- /dev/null
+++ b/src/kernel/client/kotlin/net/terramodulus/mui/gui/agim/impl/SliderComponent.kt
@@ -0,0 +1,97 @@
+/*
+ * SPDX-FileCopyrightText: 2026 TerraModulus Team and Contributors
+ * SPDX-License-Identifier: LGPL-3.0-only
+ */
+
+package net.terramodulus.mui.gui.agim.impl
+
+import com.cout970.math.vec4.Vec4i
+import net.terramodulus.mui.gui.agim.Component
+import net.terramodulus.mui.gui.asd.AsdHandle
+import net.terramodulus.mui.gui.gfx.ColorFilter
+import net.terramodulus.mui.gui.gfx.Direction4A
+import net.terramodulus.mui.gui.gfx.Direction4AD
+import net.terramodulus.mui.gui.gfx.GeneralTransform
+import net.terramodulus.mui.gui.gfx.GuiRect
+import net.terramodulus.mui.gui.gfx.RectStParams
+import net.terramodulus.mui.gui.gfx.Rectangle
+import net.terramodulus.mui.gui.gfx.RectangleD
+import net.terramodulus.mui.gui.gfx.RectangleI
+import net.terramodulus.mui.gui.gfx.RenderSystem
+import kotlin.math.roundToInt
+import kotlin.properties.Delegates
+
+class SliderComponent(
+ val dir: Direction4A,
+ canvasHandle: RenderSystem.CanvasHandle,
+ asdHandle: AsdHandle,
+ config: Config,
+) : Component(asdHandle) {
+ // Though this could be made into a Pane, it would be better to optimize this into a simple Component
+ // to reduce complexities and avoid unnecessary layout computations.
+ companion object {
+ // Kind of like resolution, since parameters of Engine geometries only accept integers at the moment.
+ private val BOUNDS = RectangleI(0, 0, 10000, 10000)
+ }
+
+ private val bgTransform = GeneralTransform()
+ private val fgTransform = GeneralTransform()
+ private val background = GuiRect(canvasHandle,
+ BOUNDS.x, BOUNDS.y, BOUNDS.width, BOUNDS.height,
+ config.bgColor.x, config.bgColor.y, config.bgColor.z, config.bgColor.w,
+ ).apply { add(bgTransform) }
+ private val foreground = GuiRect(canvasHandle,
+ BOUNDS.x, BOUNDS.y, BOUNDS.width, BOUNDS.height,
+ config.fgColor.x, config.fgColor.y, config.fgColor.z, config.bgColor.w,
+ ).apply { add(fgTransform) }
+ var fraction: Double by Delegates.observable(0.0) { _, _, value ->
+ try {
+ updateForeground(asdHandle.rect, value)
+ } catch (_: UninitializedPropertyAccessException) {}
+ }
+
+ class Config(val bgColor: Vec4i, val fgColor: Vec4i)
+
+ init {
+ asdHandle.observeRect {
+ RectStParams.fromRects(BOUNDS.toDouble(), asdHandle.rect).applyToGeneralTransform(bgTransform)
+ updateForeground(asdHandle.rect, fraction)
+ }
+ }
+
+ fun addFilter(filter: ColorFilter) {
+ background.add(filter)
+ foreground.add(filter)
+ }
+
+ private fun updateForeground(rect: RectangleD, fraction: Double) {
+ // Using division by fraction is a quick hack to apply scaling along the axis while using the same BOUNDS.
+ RectStParams.fromRects(when (dir) {
+ Direction4A.XPos -> // left to right
+ Rectangle.withDirection(
+ BOUNDS.x, BOUNDS.y, (BOUNDS.width / fraction).roundToInt(), BOUNDS.height,
+ Direction4AD.QuadOne,
+ )
+ Direction4A.XNeg -> // right to left
+ Rectangle.withDirection(
+ BOUNDS.width, BOUNDS.y, (BOUNDS.width / fraction).roundToInt(), BOUNDS.height,
+ Direction4AD.QuadTwo,
+ )
+ Direction4A.YPos -> // bottom to top
+ Rectangle.withDirection(
+ BOUNDS.x, BOUNDS.y, BOUNDS.width, (BOUNDS.height / fraction).roundToInt(),
+ Direction4AD.QuadOne,
+ )
+ Direction4A.YNeg -> // top to bottom
+ Rectangle.withDirection(
+ BOUNDS.x, BOUNDS.height, BOUNDS.width, (BOUNDS.height / fraction).roundToInt(),
+ Direction4AD.QuadFour,
+ )
+ }.toDouble(), rect).applyToGeneralTransform(fgTransform)
+ }
+
+ override fun render(renderSystem: RenderSystem) {
+ background.render(renderSystem)
+ foreground.render(renderSystem)
+ }
+}
diff --git a/src/kernel/client/kotlin/net/terramodulus/mui/gui/agim/impl/TitleScreen.kt b/src/kernel/client/kotlin/net/terramodulus/mui/gui/agim/impl/TitleScreen.kt
new file mode 100644
index 00000000..a93109a9
--- /dev/null
+++ b/src/kernel/client/kotlin/net/terramodulus/mui/gui/agim/impl/TitleScreen.kt
@@ -0,0 +1,21 @@
+/*
+ * SPDX-FileCopyrightText: 2025-2026 TerraModulus Team and Contributors
+ * SPDX-License-Identifier: LGPL-3.0-only
+ */
+
+package net.terramodulus.mui.gui.agim.impl
+
+import net.terramodulus.mui.gui.agim.Layout
+import net.terramodulus.mui.gui.gfx.RenderSystem
+import net.terramodulus.mui.gui.agim.Screen
+import net.terramodulus.mui.gui.agim.ScreenManager
+import net.terramodulus.mui.gui.asd.AsdHandle
+
+class TitleScreen(
+ managerHandle: ScreenManager.Handle,
+ asdHandle: AsdHandle.Container,
+ renderSystemHandle: RenderSystem.Handle,
+) : Screen(managerHandle, asdHandle) {
+ override val layout =
+ SingletonLayout(this, BlankComponent(ComponentAsdHandleImpl()), SingletonLayout.Config.Absolute.Full)
+}
diff --git a/src/kernel/client/kotlin/net/terramodulus/mui/gui/agim/impl/WorldInitScreen.kt b/src/kernel/client/kotlin/net/terramodulus/mui/gui/agim/impl/WorldInitScreen.kt
new file mode 100644
index 00000000..a36c277c
--- /dev/null
+++ b/src/kernel/client/kotlin/net/terramodulus/mui/gui/agim/impl/WorldInitScreen.kt
@@ -0,0 +1,158 @@
+/*
+ * SPDX-FileCopyrightText: 2026 TerraModulus Team and Contributors
+ * SPDX-License-Identifier: LGPL-3.0-only
+ */
+
+package net.terramodulus.mui.gui.agim.impl
+
+import com.cout970.math.vec4.ImmVec4i
+import net.terramodulus.mui.gui.agim.Screen
+import net.terramodulus.mui.gui.agim.ScreenManager
+import net.terramodulus.mui.gui.agim.event.ScreenEvent
+import net.terramodulus.mui.gui.asd.AsdHandle
+import net.terramodulus.mui.gui.gfx.AlphaFilter
+import net.terramodulus.mui.gui.gfx.Direction4A
+import net.terramodulus.mui.gui.gfx.GuiRect
+import net.terramodulus.mui.gui.gfx.GuiSprite
+import net.terramodulus.mui.gui.gfx.InsetsD
+import net.terramodulus.mui.gui.gfx.RectangleD
+import net.terramodulus.mui.gui.gfx.RectangleI
+import net.terramodulus.mui.gui.gfx.RenderSystem
+import net.terramodulus.void.World
+
+private const val ANI_DURATION = 1F // in second
+
+class WorldInitScreen internal constructor(
+ managerHandle: ScreenManager.Handle,
+ asdHandle: AsdHandle.Container,
+ renderSystemHandle: RenderSystem.Handle,
+) : Screen(managerHandle, asdHandle) {
+ private var stage = 0
+ private var last = System.currentTimeMillis() // timestamp in milliseconds
+ private var alphaFilter = AlphaFilter(0F)
+// private val progressBar = ProgressBar(renderSystemHandle)
+ private val progressBarComponent = SliderComponent(
+ Direction4A.XPos,
+ renderSystemHandle.canvasHandle,
+ ComponentAsdHandleImpl(),
+ // Not sure why background has to be non-transparent for this to render correctly
+ SliderComponent.Config(ImmVec4i(56, 255, 252, 255), ImmVec4i(59, 12, 120, 255)),
+ ).apply {
+ addFilter(alphaFilter)
+ }
+ override val layout = CompositeLayout(this)
+ private val progressBarImpl = ProgressBarImpl()
+ internal val progressBar: World.ProgressBar = progressBarImpl
+
+ init {
+ layout.update {
+ add(SingletonLayout(this@WorldInitScreen, GeomComponent(GuiRect(
+ renderSystemHandle.canvasHandle, 0, 0, 1, 1, 56, 255, 252, 255
+ ), RectangleD(0.0, 0.0, 1.0, 1.0), ComponentAsdHandleImpl()).apply {
+ geom.add(alphaFilter)
+ }, SingletonLayout.Config.Absolute.Full))
+ add(SingletonLayout(this@WorldInitScreen, SimplePane(ComponentAsdHandleImpl()) {
+ SingletonLayout(this, DrawablesComponent(sequenceOf(
+ DrawablesComponent.Drawable(
+ GuiSprite(
+ renderSystemHandle.canvasHandle,
+ RectangleI(0, 100, 400, 100),
+ renderSystemHandle.loadTexture("/game_logo.png")
+ )
+ ),
+ DrawablesComponent.Drawable(
+ GuiRect(renderSystemHandle.canvasHandle, 0, 0, 400, 40, 59, 12, 120, 255)
+ ),
+ DrawablesComponent.Drawable(
+ GuiRect(renderSystemHandle.canvasHandle, 5, 5, 395, 35, 56, 255, 252, 255)
+ ),
+// DrawablesComponent.Drawable(progressBar.rect),
+ ), RectangleD(0.0, 0.0, 400.0, 200.0), ComponentAsdHandleImpl()).apply {
+ addFilter(alphaFilter)
+ }, SingletonLayout.Config.Aligned(
+ SingletonLayout.Config.ObjectFit.Contain,
+ SingletonLayout.Config.AlignmentConfig.DEFAULT,
+ ))
+ }, SingletonLayout.Config.Aligned(
+ SingletonLayout.Config.Relative.Simple(0.5),
+ SingletonLayout.Config.AlignmentConfig.DEFAULT,
+ )))
+ add(SingletonLayout(this@WorldInitScreen, SimplePane(ComponentAsdHandleImpl()) {
+ SingletonLayout(this, SizedPane(ComponentAsdHandleImpl(), SimplePane(ComponentAsdHandleImpl()) {
+ SingletonLayout(this, progressBarComponent,
+ SingletonLayout.Config.Absolute.Insets(InsetsD(7.0, 167.0, 7.0, 7.0))
+ )
+ },
+ SizedPane.Config(400u, 200u)), SingletonLayout.Config.Aligned(
+ SingletonLayout.Config.ObjectFit.Contain,
+ SingletonLayout.Config.AlignmentConfig.DEFAULT,
+ )
+ )
+ }, SingletonLayout.Config.Aligned(
+ SingletonLayout.Config.Relative.Simple(0.5),
+ SingletonLayout.Config.AlignmentConfig.DEFAULT,
+ )))
+ }
+
+ addListener(ScreenEvent.Update::class.java) {
+ val current = System.currentTimeMillis()
+ val elapsed = (current - last) / 1000F // elapsed time in second at this stage
+ when (stage) {
+ 0 -> if (elapsed >= ANI_DURATION) {
+ stage = 1
+ last = current
+ alphaFilter.alpha = 1F
+ progressBarImpl.ready = true
+ } else {
+ alphaFilter.alpha = elapsed / ANI_DURATION
+ }
+
+ 1 -> {
+ if (progressBarComponent.fraction >= 1) {
+ progressBarComponent.fraction = 1.0
+ stage = 2
+ last = current
+ }
+ }
+
+ 2 -> if (elapsed >= ANI_DURATION) {
+ stage = 3
+ last = current
+ alphaFilter.alpha = 0F
+ } else {
+ alphaFilter.alpha = 1 - elapsed / ANI_DURATION
+ }
+
+// 3 -> screenManager.handle.openBefore(::TitleScreen, this)
+ 3 -> it.muiIoI.screenManager.handle.exit(1)
+ }
+ }
+ }
+
+ private inner class ProgressBarImpl : World.ProgressBar {
+ var ready = false
+ set(value) {
+ field = value
+ readyListener?.invoke()
+ }
+ var readyListener: (() -> Unit)? = null
+
+ override fun addReadyListener(listener: () -> Unit) {
+ readyListener = listener
+ if (ready) listener()
+ }
+
+ override fun setProgress(progress: Double) {
+ progressBarComponent.fraction = progress
+ }
+ }
+
+// internal class ProgressBar(renderSystemHandle: RenderSystem.Handle) {
+// val rectDim = Rectangle.withPoints(7, 7, 393, 33)
+// val length = rectDim.width
+// var progress: Float by Delegates.observable(0f) { _, _, _ ->
+// rect.setPos(7, 7, rectDim.x + (progress * length).toInt(), 33)
+// }
+// val rect = GuiRect(renderSystemHandle.canvasHandle, 7, 7, 7, 33, 240, 240, 240, 255)
+// }
+}
diff --git a/src/kernel/client/kotlin/net/terramodulus/mui/gui/agim/package-info.java b/src/kernel/client/kotlin/net/terramodulus/mui/gui/agim/package-info.java
new file mode 100644
index 00000000..5723fbd2
--- /dev/null
+++ b/src/kernel/client/kotlin/net/terramodulus/mui/gui/agim/package-info.java
@@ -0,0 +1,11 @@
+/*
+ * SPDX-FileCopyrightText: 2026 TerraModulus Team and Contributors
+ * SPDX-License-Identifier: LGPL-3.0-only
+ */
+
+/**
+ * Abstract Graphical Interface Model (AGIM)
+ *
+ * Defined in EFP 9 (or a dedicated EFP for this chapter).
+ */
+package net.terramodulus.mui.gui.agim;
diff --git a/src/kernel/client/kotlin/net/terramodulus/mui/gui/asd/AsdHandle.kt b/src/kernel/client/kotlin/net/terramodulus/mui/gui/asd/AsdHandle.kt
new file mode 100644
index 00000000..660d2c0f
--- /dev/null
+++ b/src/kernel/client/kotlin/net/terramodulus/mui/gui/asd/AsdHandle.kt
@@ -0,0 +1,59 @@
+/*
+ * SPDX-FileCopyrightText: 2026 TerraModulus Team and Contributors
+ * SPDX-License-Identifier: LGPL-3.0-only
+ */
+
+package net.terramodulus.mui.gui.asd
+
+import net.terramodulus.mui.gui.agim.AgimoProperty
+import net.terramodulus.mui.gui.agim.AgimoPropertyMap
+import net.terramodulus.mui.gui.gfx.RectangleD
+import net.terramodulus.mui.gui.gfx.RectangleF
+import net.terramodulus.util.TypedAnchorMap
+import java.util.function.BiFunction
+
+/**
+ * Since rectangles are modified only during layout processing,
+ * further follow-ups must not be deferred to next tick.
+ */
+abstract class AsdHandle internal constructor() {
+ /**
+ * Caveat: Must only be modified by [Layout][net.terramodulus.mui.gui.agim.Layout].
+ * When modified, [triggerRectObservers] must be invoked.
+ */
+ abstract var rect: RectangleD
+ internal set
+
+ protected val rectObservers = LinkedHashSet<() -> Unit>()
+
+ internal fun observeRect(observer: () -> Unit) {
+ rectObservers.add(observer)
+ }
+
+ internal fun unobserveRect(observer: () -> Unit) {
+ rectObservers.remove(observer)
+ }
+
+ internal fun triggerRectObservers() {
+ rectObservers.forEach { it() }
+ }
+
+ /**
+ * Permanent AGIMO Properties
+ */
+ val properties = AgimoPropertyMap()
+
+ /**
+ * Registers ASD Processors from AGIMOs to [AsdManager].
+ */
+ abstract fun registerAsdProcessor(processor: AsdProcessor<*>)
+
+ // TODO likely those below are useless
+ abstract class Container : AsdHandle() {}
+
+ abstract class Menu : Container() {}
+
+ abstract class Screen : Container() {}
+
+// interface Component : LayoutHandle {} // Do we need this?
+}
diff --git a/src/kernel/client/kotlin/net/terramodulus/mui/gui/asd/AsdIr.kt b/src/kernel/client/kotlin/net/terramodulus/mui/gui/asd/AsdIr.kt
new file mode 100644
index 00000000..355c613c
--- /dev/null
+++ b/src/kernel/client/kotlin/net/terramodulus/mui/gui/asd/AsdIr.kt
@@ -0,0 +1,12 @@
+/*
+ * SPDX-FileCopyrightText: 2026 TerraModulus Team and Contributors
+ * SPDX-License-Identifier: LGPL-3.0-only
+ */
+
+package net.terramodulus.mui.gui.asd
+
+/**
+ * ASD Intermediate Representation (IR)
+ */
+class AsdIr {
+}
diff --git a/src/kernel/client/kotlin/net/terramodulus/mui/gui/asd/AsdManager.kt b/src/kernel/client/kotlin/net/terramodulus/mui/gui/asd/AsdManager.kt
new file mode 100644
index 00000000..6d6e75af
--- /dev/null
+++ b/src/kernel/client/kotlin/net/terramodulus/mui/gui/asd/AsdManager.kt
@@ -0,0 +1,41 @@
+/*
+ * SPDX-FileCopyrightText: 2026 TerraModulus Team and Contributors
+ * SPDX-License-Identifier: LGPL-3.0-only
+ */
+
+package net.terramodulus.mui.gui.asd
+
+import net.terramodulus.mui.gui.agim.AgimoTreeVisitor
+
+// TODO Should ASD affect choices of Layout?
+// There are two proposals:
+// - Each Container manages a Layout, then the Layout is put into LayoutManager/LayoutViewport
+// - Each Container is totally managed by AsdManager except for Facets, but this rather complicated for ASD
+internal class AsdManager internal constructor() {
+ companion object {
+ // TODO temporary demonstrative testing default
+ internal fun default(): AsdManager = AsdManager()
+ }
+
+ private val processors = HashSet>()
+
+ internal fun registerProcessor(processor: AsdProcessor<*>) {
+ processors.add(processor)
+ }
+
+ internal inner class AgimHandle {
+ internal fun registerProcessor(processor: AsdProcessor<*>) = this@AsdManager.registerProcessor(processor)
+ }
+
+ internal fun process() {
+ fun process(processor: AsdProcessor) = processor.processDefined(processor.produceDefined())
+ processors.forEach { process(it) }
+ }
+
+ // TODO What should be the use of this
+ private inner class TreeVisitor(visitorScreens: ScreenTreeVisitor, visitorMenus: MenuTreeVisitor) : AgimoTreeVisitor() {
+ init {
+ val root = RootNode(visitorScreens.visit(), visitorMenus.visit())
+ }
+ }
+}
diff --git a/src/kernel/client/kotlin/net/terramodulus/mui/gui/asd/AsdProcessor.kt b/src/kernel/client/kotlin/net/terramodulus/mui/gui/asd/AsdProcessor.kt
new file mode 100644
index 00000000..4a50dc05
--- /dev/null
+++ b/src/kernel/client/kotlin/net/terramodulus/mui/gui/asd/AsdProcessor.kt
@@ -0,0 +1,24 @@
+/*
+ * SPDX-FileCopyrightText: 2026 TerraModulus Team and Contributors
+ * SPDX-License-Identifier: LGPL-3.0-only
+ */
+
+package net.terramodulus.mui.gui.asd
+
+import net.terramodulus.engine.BaseAsdProcessor
+
+// TODO in reality, only binary data are consumed, but
+// in testing environment, objects are created directly
+// - most likely, each AGIMO and Layout register necessary Processor
+// to consume input data and configure its managed instance of AGIMO/Layout
+abstract class AsdProcessor {
+ companion object {
+ fun get(): BaseAsdProcessor = TODO("This processor's only use is to provide InputStream")
+ }
+
+ // TODO Temporary implementation to configure ASD without binary data but produced configs
+ abstract fun processDefined(definitions: T)
+
+ // TODO Temporary implementation to produce defined configurations for testing and demo
+ abstract fun produceDefined(): T
+}
diff --git a/src/kernel/client/kotlin/net/terramodulus/mui/gui/asd/MenuStyles.kt b/src/kernel/client/kotlin/net/terramodulus/mui/gui/asd/MenuStyles.kt
new file mode 100644
index 00000000..0022ce5d
--- /dev/null
+++ b/src/kernel/client/kotlin/net/terramodulus/mui/gui/asd/MenuStyles.kt
@@ -0,0 +1,9 @@
+/*
+ * SPDX-FileCopyrightText: 2026 TerraModulus Team and Contributors
+ * SPDX-License-Identifier: LGPL-3.0-only
+ */
+
+package net.terramodulus.mui.gui.asd
+
+class MenuStyles {
+}
diff --git a/src/kernel/client/kotlin/net/terramodulus/mui/gui/asd/ScreenStyles.kt b/src/kernel/client/kotlin/net/terramodulus/mui/gui/asd/ScreenStyles.kt
new file mode 100644
index 00000000..76571c6e
--- /dev/null
+++ b/src/kernel/client/kotlin/net/terramodulus/mui/gui/asd/ScreenStyles.kt
@@ -0,0 +1,9 @@
+/*
+ * SPDX-FileCopyrightText: 2026 TerraModulus Team and Contributors
+ * SPDX-License-Identifier: LGPL-3.0-only
+ */
+
+package net.terramodulus.mui.gui.asd
+
+class ScreenStyles {
+}
diff --git a/src/kernel/client/kotlin/net/terramodulus/mui/gfx/Anchor.kt b/src/kernel/client/kotlin/net/terramodulus/mui/gui/gfx/Anchor.kt
similarity index 83%
rename from src/kernel/client/kotlin/net/terramodulus/mui/gfx/Anchor.kt
rename to src/kernel/client/kotlin/net/terramodulus/mui/gui/gfx/Anchor.kt
index b000efe7..b3880d64 100644
--- a/src/kernel/client/kotlin/net/terramodulus/mui/gfx/Anchor.kt
+++ b/src/kernel/client/kotlin/net/terramodulus/mui/gui/gfx/Anchor.kt
@@ -1,9 +1,9 @@
/*
- * SPDX-FileCopyrightText: 2025 TerraModulus Team and Contributors
+ * SPDX-FileCopyrightText: 2025-2026 TerraModulus Team and Contributors
* SPDX-License-Identifier: LGPL-3.0-only
*/
-package net.terramodulus.mui.gfx
+package net.terramodulus.mui.gui.gfx
/*
* Every set of anchor position directions has different meanings in different context,
diff --git a/src/kernel/client/kotlin/net/terramodulus/mui/gui/gfx/ColorFilter.kt b/src/kernel/client/kotlin/net/terramodulus/mui/gui/gfx/ColorFilter.kt
new file mode 100644
index 00000000..fa70486f
--- /dev/null
+++ b/src/kernel/client/kotlin/net/terramodulus/mui/gui/gfx/ColorFilter.kt
@@ -0,0 +1,13 @@
+/*
+ * SPDX-FileCopyrightText: 2025-2026 TerraModulus Team and Contributors
+ * SPDX-License-Identifier: LGPL-3.0-only
+ */
+
+package net.terramodulus.mui.gui.gfx
+
+import net.terramodulus.engine.AlphaFilter
+import net.terramodulus.engine.ColorFilter
+
+typealias ColorFilter = ColorFilter
+
+typealias AlphaFilter = AlphaFilter
diff --git a/src/kernel/client/kotlin/net/terramodulus/mui/gfx/Dimension.kt b/src/kernel/client/kotlin/net/terramodulus/mui/gui/gfx/Dimension.kt
similarity index 65%
rename from src/kernel/client/kotlin/net/terramodulus/mui/gfx/Dimension.kt
rename to src/kernel/client/kotlin/net/terramodulus/mui/gui/gfx/Dimension.kt
index 6825bcb0..ede9a02c 100644
--- a/src/kernel/client/kotlin/net/terramodulus/mui/gfx/Dimension.kt
+++ b/src/kernel/client/kotlin/net/terramodulus/mui/gui/gfx/Dimension.kt
@@ -1,14 +1,16 @@
/*
- * SPDX-FileCopyrightText: 2025 TerraModulus Team and Contributors
+ * SPDX-FileCopyrightText: 2025-2026 TerraModulus Team and Contributors
* SPDX-License-Identifier: LGPL-3.0-only
*/
-package net.terramodulus.mui.gfx
+package net.terramodulus.mui.gui.gfx
data class Dimension2I(val width: Int, val height: Int)
data class Dimension2F(val width: Float, val height: Float)
+data class Dimension2D(val width: Double, val height: Double)
+
data class Dimension3I(val width: Int, val height: Int, val length: Int)
data class Dimension3F(val width: Float, val height: Float, val length: Float)
diff --git a/src/kernel/client/kotlin/net/terramodulus/mui/gfx/Direction.kt b/src/kernel/client/kotlin/net/terramodulus/mui/gui/gfx/Direction.kt
similarity index 81%
rename from src/kernel/client/kotlin/net/terramodulus/mui/gfx/Direction.kt
rename to src/kernel/client/kotlin/net/terramodulus/mui/gui/gfx/Direction.kt
index 621776f8..b8d78bd6 100644
--- a/src/kernel/client/kotlin/net/terramodulus/mui/gfx/Direction.kt
+++ b/src/kernel/client/kotlin/net/terramodulus/mui/gui/gfx/Direction.kt
@@ -1,9 +1,9 @@
/*
- * SPDX-FileCopyrightText: 2025 TerraModulus Team and Contributors
+ * SPDX-FileCopyrightText: 2025-2026 TerraModulus Team and Contributors
* SPDX-License-Identifier: LGPL-3.0-only
*/
-package net.terramodulus.mui.gfx
+package net.terramodulus.mui.gui.gfx
/*
* Every set of directions has different meanings in different context,
@@ -11,6 +11,10 @@ package net.terramodulus.mui.gfx
* Those should be used with care since they are not already interconvertible.
*/
+enum class Direction2 {
+ Positive, Negative;
+}
+
/**
* Set of 4 compass directions.
*/
@@ -39,6 +43,13 @@ enum class Direction4A {
XPos, XNeg, YPos, YNeg;
}
+/**
+ * Set of 4 axial diagonal directions, by Cartesian quadrants.
+ */
+enum class Direction4AD {
+ QuadOne, QuadTwo, QuadThree, QuadFour;
+}
+
/**
* Set of 8 compass directions.
*/
diff --git a/src/kernel/client/kotlin/net/terramodulus/mui/gfx/GuiGeometry.kt b/src/kernel/client/kotlin/net/terramodulus/mui/gui/gfx/GuiGeometry.kt
similarity index 55%
rename from src/kernel/client/kotlin/net/terramodulus/mui/gfx/GuiGeometry.kt
rename to src/kernel/client/kotlin/net/terramodulus/mui/gui/gfx/GuiGeometry.kt
index cc6a61b4..6515e0ff 100644
--- a/src/kernel/client/kotlin/net/terramodulus/mui/gfx/GuiGeometry.kt
+++ b/src/kernel/client/kotlin/net/terramodulus/mui/gui/gfx/GuiGeometry.kt
@@ -1,9 +1,9 @@
/*
- * SPDX-FileCopyrightText: 2025 TerraModulus Team and Contributors
+ * SPDX-FileCopyrightText: 2025-2026 TerraModulus Team and Contributors
* SPDX-License-Identifier: LGPL-3.0-only
*/
-package net.terramodulus.mui.gfx
+package net.terramodulus.mui.gui.gfx
import net.terramodulus.engine.GeomDrawable
import net.terramodulus.engine.SimpleLineGeom
@@ -14,19 +14,19 @@ sealed class GuiGeometry(protected val geom: GeomDrawable) {
fun add(filter: ColorFilter) = geom.add(filter)
- fun setPos(pos: FloatArray) = geom.setPos(pos)
+ protected fun setPos(pos: FloatArray) = geom.setPos(pos)
fun render(renderSystem: RenderSystem) = renderSystem.renderGuiGeo(geom)
}
-class GuiLine(x0: Int, y0: Int, x1: Int, y1: Int, r: Int, g: Int, b: Int, a: Int) :
- GuiGeometry(SimpleLineGeom(x0, y0, x1, y1, r, g, b, a)) {
+class GuiLine(handle: RenderSystem.CanvasHandle, x0: Int, y0: Int, x1: Int, y1: Int, r: Int, g: Int, b: Int, a: Int) :
+ GuiGeometry(SimpleLineGeom(handle.canvas, x0, y0, x1, y1, r, g, b, a)) {
fun setPos(x0: Int, y0: Int, x1: Int, y1: Int) =
setPos(floatArrayOf(x0.toFloat(), y0.toFloat(), x1.toFloat(), y1.toFloat()))
}
-class GuiRect(x0: Int, y0: Int, x1: Int, y1: Int, r: Int, g: Int, b: Int, a: Int) :
- GuiGeometry(SimpleRectGeom(x0, y0, x1, y1, r, g, b, a)) {
+class GuiRect(handle: RenderSystem.CanvasHandle, x0: Int, y0: Int, x1: Int, y1: Int, r: Int, g: Int, b: Int, a: Int) :
+ GuiGeometry(SimpleRectGeom(handle.canvas, x0, y0, x1, y1, r, g, b, a)) {
fun setPos(x0: Int, y0: Int, x1: Int, y1: Int) =
setPos(floatArrayOf(x0.toFloat(), y0.toFloat(), x1.toFloat(), y1.toFloat()))
}
diff --git a/src/kernel/client/kotlin/net/terramodulus/mui/gui/gfx/GuiSprite.kt b/src/kernel/client/kotlin/net/terramodulus/mui/gui/gfx/GuiSprite.kt
new file mode 100644
index 00000000..67f869ad
--- /dev/null
+++ b/src/kernel/client/kotlin/net/terramodulus/mui/gui/gfx/GuiSprite.kt
@@ -0,0 +1,18 @@
+/*
+ * SPDX-FileCopyrightText: 2025-2026 TerraModulus Team and Contributors
+ * SPDX-License-Identifier: LGPL-3.0-only
+ */
+
+package net.terramodulus.mui.gui.gfx
+
+import net.terramodulus.engine.SpriteMesh
+
+class GuiSprite(handle: RenderSystem.CanvasHandle, val rect: RectangleI, private val texture: UInt) {
+ private val mesh = SpriteMesh(handle.canvas, rect.x, rect.y, rect.x + rect.width, rect.y + rect.height)
+
+ fun add(model: ModelTransform) = mesh.add(model)
+
+ fun add(filter: ColorFilter) = mesh.add(filter)
+
+ fun render(renderSystem: RenderSystem) = renderSystem.renderGuiTex(mesh, texture)
+}
diff --git a/src/kernel/client/kotlin/net/terramodulus/mui/gui/gfx/Insets.kt b/src/kernel/client/kotlin/net/terramodulus/mui/gui/gfx/Insets.kt
new file mode 100644
index 00000000..623d7554
--- /dev/null
+++ b/src/kernel/client/kotlin/net/terramodulus/mui/gui/gfx/Insets.kt
@@ -0,0 +1,35 @@
+/*
+ * SPDX-FileCopyrightText: 2026 TerraModulus Team and Contributors
+ * SPDX-License-Identifier: LGPL-3.0-only
+ */
+
+package net.terramodulus.mui.gui.gfx
+
+abstract class Insets(open val left: N, open val top: N, open val right: N, open val bottom: N) {
+
+}
+
+data class InsetsI(
+ override val left: Int,
+ override val top: Int,
+ override val right: Int,
+ override val bottom: Int,
+) : Insets(left, top, right, bottom)
+
+data class InsetsF(
+ override val left: Float,
+ override val top: Float,
+ override val right: Float,
+ override val bottom: Float,
+) : Insets