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 children’s preferred sizes (or zero if it has no children yet / they haven’t been laid out). The fatal interaction appears when the ScrollView is given an absolute size after it has already been inserted, or when the Stack’s 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, } @group(0) @binding(0) var uniforms: Uniforms; struct VertexOutput { @builtin(position) pos: vec4, @location(0) uv: vec2, @location(1) color: vec4, } @vertex fn vs_main(@location(0) pos: vec2, @location(1) uv: vec2, @location(2) color: vec4) -> VertexOutput { var out: VertexOutput; out.pos = uniforms.ortho * vec4(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; @group(0) @binding(2) var sampler_: sampler; @fragment fn fs_main(@location(0) uv: vec2, @location(1) color: vec4) -> @location(0) vec4 { return textureSample(texture, sampler_, uv) * color; } """), clearColor: .init(0, 0, 128, .max) ) ) graphics.scene.append(drawable) window.run() } }