Initial Commit

This commit is contained in:
Signal 2026-06-22 16:23:33 -04:00
commit 2f3600c84f
2 changed files with 333 additions and 0 deletions

View file

@ -0,0 +1,286 @@
import ArtifactPlatform
import ArtifactGraphics
import ArtifactUI
import ArtifactState
import ArtifactMath
import ArtifactFilesystem
import ArtifactColor
import Foundation
import ArgumentParser
@main
struct TestGame: AsyncParsableCommand {
@Option(name: [.short, .long], defaultAsFlag: "")
var config: String?
struct AppSettings: Codable {
var name: String
}
@MainActor
public mutating func run() async {
let atlas = STBTextProvider()
@State var thing = 0
@Settings
var settings: AppSettings = AppSettings(name: "test")
let scrollView2 = ScrollView {
VStack {
for i in 1...20 {
Text("Sub-item \(i)")
}
}
}
.height(120)
.backgroundColor(.gold)
.style(BorderWidthStyle.self, 1)
.style(BorderColorStyle.self, .blue)
let scrollView: ScrollView = ScrollView {
VStack {
for i in 1...20 {
Text("Item \(i)")
}
scrollView2
for i in 21...50 {
Text("Item \(i)")
}
Text("""
If the ScrollView has an absolute height, preferredSize correctly returns that height.
If the ScrollView has height: .auto (the default), preferredSize returns the sum of its childrens preferred sizes (or zero if it has no children yet / they havent been laid out).
The fatal interaction appears when the ScrollView is given an absolute size after it has already been inserted, or when the Stacks first measurement pass sees .auto / .grow and therefore writes a zero (or very small) height into child.frame.size.y. Subsequent layout of the ScrollView itself then sees a zero-height content area and never recovers.
""")
}
}
.height(.grow(1))
let styles = StyleSheet {
StyleRule(.type(Stack.self) / (.type(Text.self) | .type(Stack.self))) {
BackgroundColorStyle.property(Color(21, 22, 24))
BorderColorStyle.property(Color(64, 64, 64))
TextColorStyle.property(Color(100, 100, 100))
}
}
let ctx = UIContext(atlas, styles) {
HStack {
VStack {
Text("Test 1")
Text("Test 3")
HStack {
Spacer(width: 50%, height: .absolute(50))
.style(BorderRadiusStyle.self, 4)
.style(BackgroundColorStyle.self, .red)
.onClick { ev in
print("Click")
scrollView.style[BackgroundColorStyle.self] = .rgb(.random(in: 0...255), .random(in: 0...255), .random(in: 0...255))
}
.onPointerDown { ev in
print("Pointer down")
}
.onPointerUp { ev in
print("Pointer up")
}
// .onPointerEnter { ev in
// scrollView.style[BackgroundColorStyle.self] = .blue
// }
// .onPointerLeave { ev in
// scrollView.style[BackgroundColorStyle.self] = .red
// }
}
.alignment(.center)
DerivedElement {
Text("Thing: \(thing)")
}
}
.groups("foo")
.width(.grow(1))
.padding(50, 25)
.style(BorderRadiusStyle.self, 100)
.style(BorderWidthStyle.self, 6)
VStack {
scrollView
Text("Test 2")
}
.alignment(.trailing)
.width(.grow(1))
.padding(15)
}
.width(100%)
.height(100%)
}
print(scrollView2.preferredSize())
let window = SDL3Window(width: 1024, height: 720)
let graphics = WebGPUGraphics(window)
let uniforms = graphics.createBuffer(label: "Uniform buffer", usage: [.uniform, .copyDest], size: 2048)
let texture = graphics.createTexture(label: "Texture", usage: [.texture, .copyDest], size: [1024, 1024])
let sampler = graphics.createSampler(label: "Sampler")
var rgba = [UInt8](repeating: 0, count: 1024*1024*4)
for i in 0..<1024*1024 {
let gray = atlas.pixels[i]
let idx = i * 4
rgba[idx+0] = 255 // R
rgba[idx+1] = 255 // G
rgba[idx+2] = 255 // B
rgba[idx+3] = gray // A
}
texture.writeData(rgba)
var projection = Mat4.ortho(left: 0, right: Float(window.width), bottom: Float(window.height), top: 0, near: -1, far: 1)
let verts = graphics.createBuffer(label: "UI vertex buffer", usage: [.vertex, .copyDest], size: 1024 * 1024)
let inds = graphics.createBuffer(label: "UI index buffer", usage: [.index, .copyDest], size: 1024 * 1024)
var drawable = Drawable(passes: ["ui"], vertices: verts, vertexCount: 0, indices: inds, indexCount: 0, uniforms: projection.bytes)
ctx.setViewportSize(to: Vec2(1024, 720))
let list = ctx.getDrawList()
verts.writeData(list.vertices)
drawable.vertexCount = list.vertexCount
inds.writeData(list.indices.withUnsafeBytes { Array($0) })
drawable.indexCount = list.indices.count
drawable.operations = list.operations.map {
switch $0 {
case .draw(let count):
.draw(count: count)
case .pushScissor(let rect):
.pushScissor(rect: rect)
case .popScissor:
.popScissor
case .pushTransform(let transformation):
.pushTransform(transformation)
case .popTransform:
.popTransform
}
}
ctx.dump()
window.onDraw {
ctx.update(deltaTime: $0)
if ctx.needsDisplay {
let list = ctx.getDrawList()
verts.writeData(list.vertices)
drawable.vertexCount = list.vertexCount
inds.writeData(list.indices.withUnsafeBytes { Array($0) })
drawable.indexCount = list.indices.count
drawable.operations = list.operations.map {
switch $0 {
case .draw(let count):
.draw(count: count)
case .pushScissor(let rect):
.pushScissor(rect: rect)
case .popScissor:
.popScissor
case .pushTransform(let transformation):
.pushTransform(transformation)
case .popTransform:
.popTransform
}
}
}
}
window.onResize { x, y in
ctx.setViewportSize(to: Vec2d(Double(x), Double(y)))
projection = Mat4.ortho(left: 0, right: Float(window.width), bottom: Float(window.height), top: 0, near: -1, far: 1)
drawable.uniforms = projection.bytes
}
window.actions.onKeyDown(.escape) {
window.close()
}
window.actions.onKeyDown(.enter) {
scrollView.animate(duration: 1000, easing: .easeInOut) {
HeightStyle.property(50%)
// BackgroundColorStyle.property(.red)
} to: {
HeightStyle.property(100%)
// BackgroundColorStyle.property(.blue)
}
}
window.actions.onPointerDown { pointer in
ctx.handlePointerDown(pointer)
}
window.actions.onPointerMove { pointer in
ctx.handlePointerMove(pointer)
}
window.actions.onPointerUp { pointer in
ctx.handlePointerUp(pointer)
}
window.actions.onWheel { pointer, delta in
ctx.handleWheel(pointer, delta: delta)
// thing += 1
// print(thing)
// //ctx.input.scroll(amount: Vec2d(delta) * 2)
// scrollView.scrollPos += Vec2d(delta) * 4
}
graphics.registerPass(
RenderPass(
name: "ui",
label: "UI Pass",
bindings: [
.buffer(stage: .vertex, type: .uniform, buffer: uniforms),
.texture(stage: .fragment, sampleType: .float, texture: texture),
.sampler(stage: .fragment, type: .filtering, sampler: sampler)
],
vertexLayout: [.init(attributes: [.vec2f, .vec2f, .vec4n])],
vertexShader: Shader(entryPoint: "vs_main", source: """
struct Uniforms {
ortho: mat4x4<f32>,
}
@group(0) @binding(0) var<uniform> uniforms: Uniforms;
struct VertexOutput {
@builtin(position) pos: vec4<f32>,
@location(0) uv: vec2<f32>,
@location(1) color: vec4<f32>,
}
@vertex
fn vs_main(@location(0) pos: vec2<f32>, @location(1) uv: vec2<f32>, @location(2) color: vec4<f32>) -> VertexOutput {
var out: VertexOutput;
out.pos = uniforms.ortho * vec4<f32>(pos, 0.0, 1.0);
out.uv = uv;
out.color = color;
return out;
}
"""),
colorTargets: [ColorTarget(channels: .all, colorBlendOperation: .add, colorBlendSrcFactor: .srcAlpha, colorBlendDestFactor: .oneMinusSrcAlpha, alphaBlendOperation: .add, alphaBlendSrcFactor: .one, alphaBlendDestFactor: .oneMinusSrcAlpha)],
fragmentShader: Shader(entryPoint: "fs_main", source: """
@group(0) @binding(1) var texture: texture_2d<f32>;
@group(0) @binding(2) var sampler_: sampler;
@fragment
fn fs_main(@location(0) uv: vec2<f32>, @location(1) color: vec4<f32>) -> @location(0) vec4<f32> {
return textureSample(texture, sampler_, uv) * color;
}
"""),
clearColor: .init(0, 0, 128, .max)
)
)
graphics.scene.append(drawable)
window.run()
}
}