diff --git a/Examples/Menu/Sources/main.swift b/Examples/Menu/Sources/main.swift index decf599..9ceea0b 100644 --- a/Examples/Menu/Sources/main.swift +++ b/Examples/Menu/Sources/main.swift @@ -76,181 +76,210 @@ class ContentViewModel { // ----------------------------------------------------------------------------- // MARK: - Sample Usage: ContentView and App Launch // This sample content view is written using the new flexible layout system. -struct ContentView: BrewView { - let viewModel = ContentViewModel() - // Declare the UI in a computed property, similar to SwiftUI. - var body: some BrewView { - VStack(spacing: 7, alignment: .center) { - if viewModel.showCalculator { - // Calculator UI - AnyFramedView(Text(" ", frame: Frame(width: 200, height: 10))) - AnyFramedView(Text(viewModel.calculatorDisplay, - frame: Frame(width: 200, height: 25))) +// struct ContentView: BrewView { +// let viewModel = ContentViewModel() +// // Declare the UI in a computed property, similar to SwiftUI. +// var body: some BrewView { +// VStack(spacing: 7, alignment: .center) { +// if viewModel.showCalculator { +// // Calculator UI +// AnyFramedView(Text(" ", frame: Frame(width: 200, height: 10))) +// AnyFramedView(Text(viewModel.calculatorDisplay, +// frame: Frame(width: 200, height: 25))) - // Row 1: 7, 8, 9, + - AnyFramedView(HStack(spacing: 5) { - AnyFramedView(Button(text: "7", - frame: Frame(width: 45, height: 30)) { - print("Pressed 7") - viewModel.appendDigit(7) - }) - AnyFramedView(Button(text: "8", - frame: Frame(width: 45, height: 30)) { - print("Pressed 8") - viewModel.appendDigit(8) - }) - AnyFramedView(Button(text: "9", - frame: Frame(width: 45, height: 30)) { - print("Pressed 9") - viewModel.appendDigit(9) - }) - AnyFramedView(Button(text: "+", - frame: Frame(width: 45, height: 30)) { - print("Pressed +") - viewModel.setOperation("+") - }) - }) +// // Row 1: 7, 8, 9, + +// AnyFramedView(HStack(spacing: 5) { +// AnyFramedView(Button(text: "7", +// frame: Frame(width: 45, height: 30)) { +// print("Pressed 7") +// viewModel.appendDigit(7) +// }) +// AnyFramedView(Button(text: "8", +// frame: Frame(width: 45, height: 30)) { +// print("Pressed 8") +// viewModel.appendDigit(8) +// }) +// AnyFramedView(Button(text: "9", +// frame: Frame(width: 45, height: 30)) { +// print("Pressed 9") +// viewModel.appendDigit(9) +// }) +// AnyFramedView(Button(text: "+", +// frame: Frame(width: 45, height: 30)) { +// print("Pressed +") +// viewModel.setOperation("+") +// }) +// }) - // Row 2: 4, 5, 6, - - AnyFramedView(HStack(spacing: 5) { - AnyFramedView(Button(text: "4", - frame: Frame(width: 45, height: 30)) { - print("Pressed 4") - viewModel.appendDigit(4) - }) - AnyFramedView(Button(text: "5", - frame: Frame(width: 45, height: 30)) { - print("Pressed 5") - viewModel.appendDigit(5) - }) - AnyFramedView(Button(text: "6", - frame: Frame(width: 45, height: 30)) { - print("Pressed 6") - viewModel.appendDigit(6) - }) - AnyFramedView(Button(text: "-", - frame: Frame(width: 45, height: 30)) { - print("Pressed -") - viewModel.setOperation("-") - }) - }) +// // Row 2: 4, 5, 6, - +// AnyFramedView(HStack(spacing: 5) { +// AnyFramedView(Button(text: "4", +// frame: Frame(width: 45, height: 30)) { +// print("Pressed 4") +// viewModel.appendDigit(4) +// }) +// AnyFramedView(Button(text: "5", +// frame: Frame(width: 45, height: 30)) { +// print("Pressed 5") +// viewModel.appendDigit(5) +// }) +// AnyFramedView(Button(text: "6", +// frame: Frame(width: 45, height: 30)) { +// print("Pressed 6") +// viewModel.appendDigit(6) +// }) +// AnyFramedView(Button(text: "-", +// frame: Frame(width: 45, height: 30)) { +// print("Pressed -") +// viewModel.setOperation("-") +// }) +// }) - // Row 3: 1, 2, 3, * - AnyFramedView(HStack(spacing: 5) { - AnyFramedView(Button(text: "1", - frame: Frame(width: 45, height: 30)) { - print("Pressed 1") - viewModel.appendDigit(1) - }) - AnyFramedView(Button(text: "2", - frame: Frame(width: 45, height: 30)) { - print("Pressed 2") - viewModel.appendDigit(2) - }) - AnyFramedView(Button(text: "3", - frame: Frame(width: 45, height: 30)) { - print("Pressed 3") - viewModel.appendDigit(3) - }) - AnyFramedView(Button(text: "*", - frame: Frame(width: 45, height: 30)) { - print("Pressed *") - viewModel.setOperation("*") - }) - }) +// // Row 3: 1, 2, 3, * +// AnyFramedView(HStack(spacing: 5) { +// AnyFramedView(Button(text: "1", +// frame: Frame(width: 45, height: 30)) { +// print("Pressed 1") +// viewModel.appendDigit(1) +// }) +// AnyFramedView(Button(text: "2", +// frame: Frame(width: 45, height: 30)) { +// print("Pressed 2") +// viewModel.appendDigit(2) +// }) +// AnyFramedView(Button(text: "3", +// frame: Frame(width: 45, height: 30)) { +// print("Pressed 3") +// viewModel.appendDigit(3) +// }) +// AnyFramedView(Button(text: "*", +// frame: Frame(width: 45, height: 30)) { +// print("Pressed *") +// viewModel.setOperation("*") +// }) +// }) - // Row 4: 0, C, =, / - AnyFramedView(HStack(spacing: 5) { - AnyFramedView(Button(text: "0", - frame: Frame(width: 45, height: 30)) { - print("Pressed 0") - viewModel.appendDigit(0) - }) - AnyFramedView(Button(text: "C", - frame: Frame(width: 45, height: 30)) { - print("Pressed C") - viewModel.clear() - }) - AnyFramedView(Button(text: "=", - frame: Frame(width: 45, height: 30)) { - print("Pressed =") - viewModel.calculate() - }) - AnyFramedView(Button(text: "/", - frame: Frame(width: 45, height: 30)) { - print("Pressed /") - viewModel.setOperation("/") - }) - }) +// // Row 4: 0, C, =, / +// AnyFramedView(HStack(spacing: 5) { +// AnyFramedView(Button(text: "0", +// frame: Frame(width: 45, height: 30)) { +// print("Pressed 0") +// viewModel.appendDigit(0) +// }) +// AnyFramedView(Button(text: "C", +// frame: Frame(width: 45, height: 30)) { +// print("Pressed C") +// viewModel.clear() +// }) +// AnyFramedView(Button(text: "=", +// frame: Frame(width: 45, height: 30)) { +// print("Pressed =") +// viewModel.calculate() +// }) +// AnyFramedView(Button(text: "/", +// frame: Frame(width: 45, height: 30)) { +// print("Pressed /") +// viewModel.setOperation("/") +// }) +// }) - // Back button - AnyFramedView(Button(text: "BACK", - frame: Frame(width: 200, height: 30)) { - print("Exiting calculator") - viewModel.showCalculator = false - }) - } else { - // Main menu UI - AnyFramedView(Text(" ", - frame: Frame(width: 200, height: 10))) - AnyFramedView(Text("EARBII", - frame: Frame(width: 200, height: 25))) +// // Back button +// AnyFramedView(Button(text: "BACK", +// frame: Frame(width: 200, height: 30)) { +// print("Exiting calculator") +// viewModel.showCalculator = false +// }) +// } else { +// // Main menu UI +// AnyFramedView(Text(" ", +// frame: Frame(width: 200, height: 10))) +// AnyFramedView(Text("EARBII", +// frame: Frame(width: 200, height: 25))) - AnyFramedView(Button(text: "CALCULATOR", - frame: Frame(width: 200, height: 30)) { - print("Calculator selected") - viewModel.showCalculator = true - }) - AnyFramedView(Button(text: "OPTION B", - frame: Frame(width: 200, height: 30)) { - print("Option B selected") - }) - if !viewModel.showExtraOption { - AnyFramedView(Button(text: "MORE", - frame: Frame(width: 200, height: 30)) { - print("More selected") - viewModel.showExtraOption.toggle() - }) - } else { - AnyFramedView(Button(text: "COLLAPSE", - frame: Frame(width: 200, height: 30)) { - print("Back selected") - viewModel.showExtraOption.toggle() - }) - } +// AnyFramedView(Button(text: "CALCULATOR", +// frame: Frame(width: 200, height: 30)) { +// print("Calculator selected") +// viewModel.showCalculator = true +// }) +// AnyFramedView(Button(text: "OPTION B", +// frame: Frame(width: 200, height: 30)) { +// print("Option B selected") +// }) +// if !viewModel.showExtraOption { +// AnyFramedView(Button(text: "MORE", +// frame: Frame(width: 200, height: 30)) { +// print("More selected") +// viewModel.showExtraOption.toggle() +// }) +// } else { +// AnyFramedView(Button(text: "COLLAPSE", +// frame: Frame(width: 200, height: 30)) { +// print("Back selected") +// viewModel.showExtraOption.toggle() +// }) +// } - if viewModel.showExtraOption { - AnyFramedView(Button(text: "EXTRA", - frame: Frame(width: 200, height: 30)) { - print("Extra Option selected") - }) - } else { - AnyFramedView(EmptyView()) - } +// if viewModel.showExtraOption { +// AnyFramedView(Button(text: "EXTRA", +// frame: Frame(width: 200, height: 30)) { +// print("Extra Option selected") +// }) +// } else { +// AnyFramedView(EmptyView()) +// } - if !viewModel.showExtraOption { - AnyFramedView(HStack(spacing: 10) { - AnyFramedView(Button(text: "X", - frame: Frame(width: 60, height: 30)) { - print("Option C selected") - }) - AnyFramedView(Button(text: "Y", - frame: Frame(width: 60, height: 30)) { - print("Option D selected") - }) - AnyFramedView(Button(text: "Z", - frame: Frame(width: 60, height: 30)) { - print("Option E selected") - }) - }) - } else { - AnyFramedView(EmptyView()) - } - } +// if !viewModel.showExtraOption { +// AnyFramedView(HStack(spacing: 10) { +// AnyFramedView(Button(text: "X", +// frame: Frame(width: 60, height: 30)) { +// print("Option C selected") +// }) +// AnyFramedView(Button(text: "Y", +// frame: Frame(width: 60, height: 30)) { +// print("Option D selected") +// }) +// AnyFramedView(Button(text: "Z", +// frame: Frame(width: 60, height: 30)) { +// print("Option E selected") +// }) +// }) +// } else { +// AnyFramedView(EmptyView()) +// } +// } +// } +// } + +// // Render the computed body. +// func render(in context: inout BrewUIContext) { +// body.render(in: &context) +// } +// } + +struct ContentView: BrewView { + let viewModel = ContentViewModel() + let pixelCat = PixelCat(x: 100, y: 100) + + var body: some BrewView { + VStack(spacing: 7, alignment: .center) { + // Your existing UI code + + // To start the cat walking: + AnyFramedView(Button(text: "WALK CAT", + frame: Frame(width: 200, height: 30)) { + pixelCat.walk(direction: -1.0) // No mutating needed + }) + + // To stop the cat: + AnyFramedView(Button(text: "STOP CAT", + frame: Frame(width: 200, height: 30)) { + pixelCat.sit() // No mutating needed + }) + + AnyFramedView(pixelCat) } } - // Render the computed body. func render(in context: inout BrewUIContext) { body.render(in: &context) } @@ -277,7 +306,7 @@ buzzer.suspend() // Buzzer off initially let contentView = ContentView() let myFontConfig = FontConfiguration( - defaultFontPath: "/SD:/AveriaSansLibre-Bold.ttf", + defaultFontPath: "/lfs/Resources/Fonts/Roboto-Regular.ttf", defaultPointSize: 10, defaultDPI: 240 ) diff --git a/Sources/BrewUI/BrewUI.swift b/Sources/BrewUI/BrewUI.swift index 3982f0f..9a42196 100644 --- a/Sources/BrewUI/BrewUI.swift +++ b/Sources/BrewUI/BrewUI.swift @@ -1352,3 +1352,155 @@ public struct ZStack: BrewView, FramedView, OffsetRenderable { } } } + +// Simple Pixel Cat Animation +public class PixelCat:BrewView, FramedView, OffsetRenderable{ + public let frame: Frame + private var position: Point + private var frameIndex: Int = 0 + private var frameTimer: Int = 0 + private var isWalking: Bool = false + private var velocity: Float = 0 + + // Cat sprite frames + private let sittingFrames: [[[Int]]] = [ + // Frame 1 + [[0,0,0,0,0,0,0,0], + [0,1,1,0,0,1,1,0], + [0,1,1,0,0,1,1,0], + [0,2,2,2,2,2,2,0], + [0,2,3,2,2,3,2,0], + [0,2,2,2,2,2,2,0], + [0,2,2,1,1,2,2,0], + [0,2,2,2,2,2,2,0]], + // Frame 2 (blinking) + [[0,0,0,0,0,0,0,0], + [0,1,1,0,0,1,1,0], + [0,1,1,0,0,1,1,0], + [0,2,2,2,2,2,2,0], + [0,2,1,2,2,1,2,0], + [0,2,2,2,2,2,2,0], + [0,2,2,1,1,2,2,0], + [0,2,2,2,2,2,2,0]] + ] + + private let walkingFrames: [[[Int]]] = [ + // Frame 1 + [[0,0,0,0,0,0,0,0], + [0,1,1,0,0,1,1,0], + [0,1,1,0,0,1,1,0], + [0,2,2,2,2,2,2,0], + [0,2,3,2,2,3,2,0], + [0,2,2,2,2,2,2,0], + [0,2,0,2,2,0,2,0], + [0,2,0,2,0,2,0,0]], + // Frame 2 + [[0,0,0,0,0,0,0,0], + [0,1,1,0,0,1,1,0], + [0,1,1,0,0,1,1,0], + [0,2,2,2,2,2,2,0], + [0,2,3,2,2,3,2,0], + [0,2,2,2,2,2,2,0], + [0,0,2,0,0,2,0,0], + [0,2,0,0,2,0,2,0]] + ] + + // Color map + private let colors: [UInt32] = [ + 0x00000000, // 0: Transparent + 0xFF000000, // 1: Black + 0xFFFFA500, // 2: Orange + 0xFFFFFFFF // 3: White + ] + + public init(x: Int, y: Int) { + self.frame = Frame(x: x, y: y, width: 16, height: 16) + self.position = Point(x: x, y: y) + print("Pixel cat Initialized") + } + + // Start walking animation (no mutating needed for class) + public func walk(direction: Float) { + isWalking = true + velocity = direction + } + + // Stop and sit (no mutating needed for class) + public func sit() { + isWalking = false + velocity = 0 + frameIndex = 0 + } + + // Render the cat (no mutating needed for class) + public func render(in context: inout BrewUIContext) { + // Update position if walking + if isWalking { + print("Pixel cat is walking") + // Clear previous position + drawCurrentFrame(context.layer, clear: true) + + // Update position (simple animation without delta time) + position.x += Int(velocity) + + // Check boundaries + if position.x < 10 || position.x > context.width - 26 { + velocity = -velocity + } + } + + // Update animation frame + frameTimer += 1 + if frameTimer > 1 { // Change frame every 5 renders (adjust for speed) + frameTimer = 0 + frameIndex = (frameIndex + 1) % (isWalking ? walkingFrames.count : sittingFrames.count) + } + + // Draw the cat + drawCurrentFrame(context.layer, clear: false) + print("Pixel cat drawn") + } + + public func render(withOffsetX offsetX: Int, offsetY: Int, in context: inout BrewUIContext) { + // Store original position + let originalX = position.x + let originalY = position.y + + // Temporarily move to the offset position + position.x += offsetX + position.y += offsetY + + // Render at the offset position + render(in: &context) + + // Restore original position + position.x = originalX + position.y = originalY + } + // Helper to draw the current frame + private func drawCurrentFrame(_ layer: Layer, clear: Bool) { + let frames = isWalking ? walkingFrames : sittingFrames + let frame = frames[frameIndex] + let size = 15 // Scale factor for the cat + + for y in 0.. 0 { // Skip transparent pixels + let color = clear ? 0x00000000 : colors[colorIndex] + + layer.draw { canvas in + canvas.fillRectangle( + at: Point(x: position.x + x*size, y: position.y + y*size), + width: size, + height: size, + data: color + ) + } + } + } + } + } + } +} \ No newline at end of file diff --git a/Sources/BrewUI/PixelCat.swift b/Sources/BrewUI/PixelCat.swift new file mode 100644 index 0000000..abae26f --- /dev/null +++ b/Sources/BrewUI/PixelCat.swift @@ -0,0 +1,152 @@ +// Simple Pixel Cat Animation +class PixelCat{ + public let frame: Frame + private var position: Point + private var frameIndex: Int = 0 + private var frameTimer: Int = 0 + private var isWalking: Bool = false + private var velocity: Float = 0 + + // Cat sprite frames + private let sittingFrames: [[[Int]]] = [ + // Frame 1 + [[0,0,0,0,0,0,0,0], + [0,1,1,0,0,1,1,0], + [0,1,1,0,0,1,1,0], + [0,2,2,2,2,2,2,0], + [0,2,3,2,2,3,2,0], + [0,2,2,2,2,2,2,0], + [0,2,2,1,1,2,2,0], + [0,2,2,2,2,2,2,0]], + // Frame 2 (blinking) + [[0,0,0,0,0,0,0,0], + [0,1,1,0,0,1,1,0], + [0,1,1,0,0,1,1,0], + [0,2,2,2,2,2,2,0], + [0,2,1,2,2,1,2,0], + [0,2,2,2,2,2,2,0], + [0,2,2,1,1,2,2,0], + [0,2,2,2,2,2,2,0]] + ] + + private let walkingFrames: [[[Int]]] = [ + // Frame 1 + [[0,0,0,0,0,0,0,0], + [0,1,1,0,0,1,1,0], + [0,1,1,0,0,1,1,0], + [0,2,2,2,2,2,2,0], + [0,2,3,2,2,3,2,0], + [0,2,2,2,2,2,2,0], + [0,2,0,2,2,0,2,0], + [0,2,0,2,0,2,0,0]], + // Frame 2 + [[0,0,0,0,0,0,0,0], + [0,1,1,0,0,1,1,0], + [0,1,1,0,0,1,1,0], + [0,2,2,2,2,2,2,0], + [0,2,3,2,2,3,2,0], + [0,2,2,2,2,2,2,0], + [0,0,2,0,0,2,0,0], + [0,2,0,0,2,0,2,0]] + ] + + // Color map + private let colors: [UInt32] = [ + 0x00000000, // 0: Transparent + 0xFF000000, // 1: Black + 0xFFFFA500, // 2: Orange + 0xFFFFFFFF // 3: White + ] + + init(x: Int, y: Int) { + self.frame = Frame(x: x, y: y, width: 16, height: 16) + self.position = Point(x: x, y: y) + } + + // Start walking animation (no mutating needed for class) + func walk(direction: Float) { + isWalking = true + velocity = direction + } + + // Stop and sit (no mutating needed for class) + func sit() { + isWalking = false + velocity = 0 + frameIndex = 0 + } + + // Render the cat (no mutating needed for class) + func render(in context: inout BrewUIContext) { + // Update position if walking + if isWalking { + // Clear previous position + drawCurrentFrame(context.layer, clear: true) + + // Update position (simple animation without delta time) + position.x += Int(velocity) + + // Check boundaries + if position.x < 10 || position.x > context.width - 26 { + velocity = -velocity + } + } + + // Update animation frame + frameTimer += 1 + if frameTimer >= 5 { // Change frame every 5 renders (adjust for speed) + frameTimer = 0 + frameIndex = (frameIndex + 1) % (isWalking ? walkingFrames.count : sittingFrames.count) + } + + // Draw the cat + drawCurrentFrame(context.layer, clear: false) + } + + // Helper to draw the current frame + private func drawCurrentFrame(_ layer: Layer, clear: Bool) { + let frames = isWalking ? walkingFrames : sittingFrames + let frame = frames[frameIndex] + let size = 2 // Scale factor for the cat + + for y in 0.. 0 { // Skip transparent pixels + let color = clear ? 0x00000000 : colors[colorIndex] + + layer.draw { canvas in + canvas.fillRectangle( + at: Point(x: position.x + x*size, y: position.y + y*size), + width: size, + height: size, + data: color + ) + } + } + } + } + } + } +} + +// Extension to support OffsetRenderable +extension PixelCat{ + func render(withOffsetX offsetX: Int, offsetY: Int, in context: inout BrewUIContext) { + // Store original position + let originalX = position.x + let originalY = position.y + + // Temporarily move to the offset position + position.x += offsetX + position.y += offsetY + + // Render at the offset position + render(in: &context) + + // Restore original position + position.x = originalX + position.y = originalY + } +} \ No newline at end of file