Last year I did a lot of Yak Shaving, I tried to add Yoga and flex-box to Babylon.js and it kinda worked but it was still a mess. What I realized I really missed for a declarative way to describe the UI. I’ve built apps imperatively in UIKit and and Android Layouts. Its doable but its not a great experience. Apple and Google have moved on to SwiftUI and JetpackCompose and MAN what a difference.
So, I wrapped the Babylon.js GUI objects in a fluent api, added reactive state management and tried to get as close to the SwiftUI/Jetpack experience as I could. It can’t get all the way there, its still a retained mode ui where state is managed in the component and we don’t have the luxury of changing the language to suite the UI system. But with a view model and the fluent api it makes managing complex screens more delightful, at least for me it did.
import {
DisposeBag,
FluentBehaviourState,
FluentContainer,
FluentRectangle,
FluentSimpleButton,
FluentStackPanel,
FluentTextBlock,
Ref,
} from "@abraini/fluent-gui"
// Create a dispose bag to manage event listeners
const disposeBag = new DisposeBag()
// Create a reference to a button
const buttonRef = new Ref<Button>()
// Create a state for the headline text
const headlineState = new FluentBehaviourState<string>("Headline")
// Create a container and add controls to it
new FluentContainer("root",
new FluentStackPanel("stack panel",
new FluentRectangle("border")
.width("200px")
.height("100px")
.background("green")
.onPointerClick((eventData, eventState) => {
console.log("Rectangle clicked!")
}, disposeBag)
.onPointerEnter((eventData, eventState) => {
console.log("Pointer entered!")
}, disposeBag)
.modifyControl((c) => (c.thickness = 5)),
new FluentTextBlock("tb", "Headline")
.bindState(headlineState, (c, v) =>
// When headlineState changes I update my text
c.setText(v)
),
new FluentSimpleButton("simpleButton", "Click Me: 0")
// Button in stored in a ref so it can be used later
.storeIn(buttonRef)
.onPointerClick((eventData, eventState) => {
console.log("Button clicked!")
}, disposeBag)
)
.setHorizontal()
)
.build()
// Update the headline state
headlineState.setValue("Headline 2")
let count = 1
setInterval(() => {
// Access to the button in the stack panel
count += 1
const button = buttonRef.get()
button.textBlock.text = "Click Me: " + count
}, 1000)
// Dispose of all event listeners when done
disposeBag.dispose()
It let me make complex UIs with lots of interaction, but I’m still no UX designer so it looks like something out of the 80s