| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556 |
- //
- // Copyright 2026 Aarav Ravindra Kharade
- //
- // Licensed under the Apache License, Version 2.0 (the "License");
- // you may not use this file except in compliance with the License.
- // You may obtain a copy of the License at
- //
- // http://www.apache.org/licenses/LICENSE-2.0
- //
- // Unless required by applicable law or agreed to in writing, software
- // distributed under the License is distributed on an "AS IS" BASIS,
- // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- // See the License for the specific language governing permissions and
- // limitations under the License.
- //
- import ark.sys.libc
- // ═══════════════════════════════════════════════════════════════════
- // ArkUI — Declarative UI Framework for ArkOS
- // ═══════════════════════════════════════════════════════════════════
- // Provides SwiftUI-inspired view primitives (VStack, HStack, Text,
- // Button) with layout modifiers (padding, frame, background, glow,
- // outline). All rendering goes through ArkGraphics.
- //
- // Coordinate system: Cartesian (0,0 = bottom-left).
- // ═══════════════════════════════════════════════════════════════════
- // MARK: - Core Geometry Types
- /// Size in pixels (width × height).
- public struct ArkSize {
- public var w: Int
- public var h: Int
- public init(w: Int, h: Int) { self.w = w; self.h = h }
- }
- /// Point in Cartesian space (0,0 = bottom-left).
- public struct ArkPoint {
- public var x: Int
- public var y: Int
- public init(x: Int, y: Int) { self.x = x; self.y = y }
- }
- /// Rectangle defined by origin (x,y) and size (w,h).
- public struct ArkRect {
- public var x: Int
- public var y: Int
- public var w: Int
- public var h: Int
- public init(x: Int, y: Int, w: Int, h: Int) {
- self.x = x; self.y = y; self.w = w; self.h = h
- }
-
- /// Returns true if point (px, py) lies within this rectangle.
- public func contains(_ px: Int, _ py: Int) -> Bool {
- return px >= x && px < x + w && py >= y && py < y + h
- }
- }
- /// RGBA color value.
- public struct ArkColor {
- public var r: UInt8
- public var g: UInt8
- public var b: UInt8
- public var a: UInt8
- public init(r: UInt8, g: UInt8, b: UInt8, a: UInt8) {
- self.r = r; self.g = g; self.b = b; self.a = a
- }
- public static let clear = ArkColor(r: 0, g: 0, b: 0, a: 0)
- public static let white = ArkColor(r: 255, g: 255, b: 255, a: 255)
- public static let black = ArkColor(r: 0, g: 0, b: 0, a: 255)
- }
- // MARK: - UI Context
- /// Rendering context passed through the view tree during layout and render.
- public class ArkUIContext {
- public let graphics: ArkGraphics
- public let writer: ArkWrite
- public let shapes: ArkShapes
-
- public init(graphics: ArkGraphics) {
- self.graphics = graphics
- self.writer = ArkWrite(graphics: graphics)
- self.shapes = ArkShapes(graphics: graphics)
- }
- }
- // MARK: - View Protocol
- /// Base protocol for all UI views. Every view must be able to:
- /// 1. Report its ideal size given a size proposal.
- /// 2. Render itself into a given bounds rectangle.
- /// 3. Handle input events at a given point.
- public protocol ArkView {
- func sizeThatFits(proposal: ArkSize) -> ArkSize
- func render(context: ArkUIContext, bounds: ArkRect)
- func handleInput(event: ArkInputEvent, point: ArkPoint) -> Bool
- }
- /// Type-erased wrapper for any ArkView. Enables heterogeneous view storage.
- public struct AnyArkView: ArkView {
- private let _sizeThatFits: (ArkSize) -> ArkSize
- private let _render: (ArkUIContext, ArkRect) -> Void
- private let _handleInput: (ArkInputEvent, ArkPoint) -> Bool
-
- public init<V: ArkView>(_ view: V) {
- self._sizeThatFits = view.sizeThatFits
- self._render = view.render
- self._handleInput = view.handleInput
- }
-
- public func sizeThatFits(proposal: ArkSize) -> ArkSize { return _sizeThatFits(proposal) }
- public func render(context: ArkUIContext, bounds: ArkRect) { _render(context, bounds) }
- public func handleInput(event: ArkInputEvent, point: ArkPoint) -> Bool { return _handleInput(event, point) }
- }
- // MARK: - Content Alignment
- /// Alignment options for layout containers and frame views.
- public enum ArkAlignment {
- case leading, center, trailing, top, bottom
- }
- // MARK: - Layout Containers
- /// Vertical stack layout — arranges children top-to-bottom with spacing.
- public struct VStack: ArkView {
- var alignment: ArkAlignment = .center
- var spacing: Int = 10
- var children: [AnyArkView]
-
- public init(alignment: ArkAlignment = .center, spacing: Int = 10, @ArkViewBuilder content: () -> [AnyArkView]) {
- self.alignment = alignment
- self.spacing = spacing
- self.children = content()
- }
-
- public func sizeThatFits(proposal: ArkSize) -> ArkSize {
- var totalW = 0
- var totalH = 0
- for child in children {
- let size = child.sizeThatFits(proposal: proposal)
- if size.w > totalW { totalW = size.w }
- totalH += size.h
- }
- totalH += spacing * max(0, children.count - 1)
- return ArkSize(w: totalW, h: totalH)
- }
-
- public func render(context: ArkUIContext, bounds: ArkRect) {
- let totalH = sizeThatFits(proposal: ArkSize(w: bounds.w, h: bounds.h)).h
-
- // Vertical alignment: determine starting Y position
- // In Cartesian coords, top = higher Y values
- var currentY: Int
- switch alignment {
- case .top:
- currentY = bounds.y + bounds.h - 1 // Start from top of bounds
- case .bottom:
- currentY = bounds.y + totalH - 1 // Pack at bottom
- default: // .center and horizontal alignments
- currentY = bounds.y + bounds.h - (bounds.h - totalH) / 2 - 1
- }
-
- for child in children {
- let size = child.sizeThatFits(proposal: ArkSize(w: bounds.w, h: bounds.h))
-
- // Horizontal alignment within the VStack
- var childX = bounds.x
- if alignment == .center {
- childX = bounds.x + (bounds.w - size.w) / 2
- } else if alignment == .trailing {
- childX = bounds.x + bounds.w - size.w
- }
-
- // Place child: currentY is the top edge, child renders downward
- let childY = currentY - size.h + 1
- child.render(context: context, bounds: ArkRect(x: childX, y: childY, w: size.w, h: size.h))
- currentY -= size.h + spacing
- }
- }
-
- public func handleInput(event: ArkInputEvent, point: ArkPoint) -> Bool {
- for child in children {
- if child.handleInput(event: event, point: point) { return true }
- }
- return false
- }
- }
- /// Horizontal stack layout — arranges children left-to-right with spacing.
- public struct HStack: ArkView {
- var alignment: ArkAlignment = .center
- var spacing: Int = 10
- var children: [AnyArkView]
-
- public init(alignment: ArkAlignment = .center, spacing: Int = 10, @ArkViewBuilder content: () -> [AnyArkView]) {
- self.alignment = alignment
- self.spacing = spacing
- self.children = content()
- }
-
- public func sizeThatFits(proposal: ArkSize) -> ArkSize {
- var totalW = 0
- var totalH = 0
- for child in children {
- let size = child.sizeThatFits(proposal: proposal)
- if size.h > totalH { totalH = size.h }
- totalW += size.w
- }
- totalW += spacing * max(0, children.count - 1)
- return ArkSize(w: totalW, h: totalH)
- }
-
- public func render(context: ArkUIContext, bounds: ArkRect) {
- let totalW = sizeThatFits(proposal: ArkSize(w: bounds.w, h: bounds.h)).w
-
- // Horizontal alignment: determine starting X position
- var currentX: Int
- switch alignment {
- case .trailing:
- currentX = bounds.x + bounds.w - totalW
- case .leading:
- currentX = bounds.x
- default: // .center
- currentX = bounds.x + (bounds.w - totalW) / 2
- }
-
- for child in children {
- let size = child.sizeThatFits(proposal: ArkSize(w: bounds.w, h: bounds.h))
-
- // Vertical alignment within the HStack
- var childY = bounds.y
- if alignment == .center {
- childY = bounds.y + (bounds.h - size.h) / 2
- } else if alignment == .top {
- childY = bounds.y + bounds.h - size.h
- }
-
- child.render(context: context, bounds: ArkRect(x: currentX, y: childY, w: size.w, h: size.h))
- currentX += size.w + spacing
- }
- }
-
- public func handleInput(event: ArkInputEvent, point: ArkPoint) -> Bool {
- for child in children {
- if child.handleInput(event: event, point: point) { return true }
- }
- return false
- }
- }
- // MARK: - Text Component
- /// Renders a text string using the embedded Roboto Bold bitmap font.
- public struct Text: ArkView {
- public var content: String
- public var size: Int = 30
- public var color: ArkColor = .white
-
- public init(_ content: String) {
- self.content = content
- }
-
- /// Computes the exact pixel size of the rendered text using the same
- /// bounding-box logic as ArkWrite.drawText, ensuring layout matches rendering.
- public func sizeThatFits(proposal: ArkSize) -> ArkSize {
- return ArkWrite.measureText(content, size: size)
- }
-
- public func render(context: ArkUIContext, bounds: ArkRect) {
- context.writer.drawText(content, x: bounds.x, y: bounds.y, size: size, r: color.r, g: color.g, b: color.b)
- }
-
- public func handleInput(event: ArkInputEvent, point: ArkPoint) -> Bool { return false }
- }
- // MARK: - Button Components
- /// Simple button that renders a label and triggers an action on click.
- /// Stores its rendered bounds for hit testing.
- public class Button: ArkView {
- var action: () -> Void
- var label: AnyArkView
- var bg: ArkColor = .clear
- var radius: Int = 0
- var frame: ArkRect = ArkRect(x: 0, y: 0, w: 0, h: 0)
-
- public init(action: @escaping () -> Void, @ArkViewBuilder label: () -> [AnyArkView]) {
- self.action = action
- self.label = label()[0]
- }
-
- public func sizeThatFits(proposal: ArkSize) -> ArkSize {
- return label.sizeThatFits(proposal: proposal)
- }
-
- public func render(context: ArkUIContext, bounds: ArkRect) {
- // Store bounds for hit testing
- self.frame = bounds
-
- if bg.a > 0 {
- context.shapes.fillRoundedRect(x: bounds.x, y: bounds.y, w: bounds.w, h: bounds.h, radius: radius, r: bg.r, g: bg.g, b: bg.b)
- }
- label.render(context: context, bounds: bounds)
- }
-
- public func handleInput(event: ArkInputEvent, point: ArkPoint) -> Bool {
- // BTN_LEFT press (type=1, code=0x110, value=1)
- if event.type == 1 && event.value == 1 {
- if frame.contains(point.x, point.y) {
- action()
- return true
- }
- }
- return false
- }
- }
- /// Stateful button that captures its rendered frame for accurate hit testing.
- /// Used in the setup app for the "Get Started" button.
- public class StatefulButton: ArkView {
- var action: () -> Void
- var label: AnyArkView
- var frame: ArkRect = ArkRect(x: 0, y: 0, w: 0, h: 0)
-
- public init(action: @escaping () -> Void, label: AnyArkView) {
- self.action = action
- self.label = label
- }
-
- public func sizeThatFits(proposal: ArkSize) -> ArkSize {
- return label.sizeThatFits(proposal: proposal)
- }
-
- public func render(context: ArkUIContext, bounds: ArkRect) {
- self.frame = bounds
- label.render(context: context, bounds: bounds)
- }
-
- public func handleInput(event: ArkInputEvent, point: ArkPoint) -> Bool {
- // BTN_LEFT press (type=1 = EV_KEY, value=1 = press)
- if event.type == 1 && event.value == 1 {
- if frame.contains(point.x, point.y) {
- action()
- return true
- }
- }
- return false
- }
- }
- // MARK: - View Modifiers
- /// Adds equal padding on all edges around the content view.
- /// Correctly reduces the available proposal for the child.
- public struct PaddingView: ArkView {
- var content: AnyArkView
- var edges: Int
-
- public func sizeThatFits(proposal: ArkSize) -> ArkSize {
- // Pass reduced proposal to the child so it knows how much space it actually has
- let childProposal = ArkSize(w: max(0, proposal.w - edges * 2), h: max(0, proposal.h - edges * 2))
- let child = content.sizeThatFits(proposal: childProposal)
- return ArkSize(w: child.w + edges * 2, h: child.h + edges * 2)
- }
-
- public func render(context: ArkUIContext, bounds: ArkRect) {
- let innerBounds = ArkRect(
- x: bounds.x + edges,
- y: bounds.y + edges,
- w: max(0, bounds.w - edges * 2),
- h: max(0, bounds.h - edges * 2)
- )
- content.render(context: context, bounds: innerBounds)
- }
-
- public func handleInput(event: ArkInputEvent, point: ArkPoint) -> Bool {
- return content.handleInput(event: event, point: point)
- }
- }
- /// Adds a colored glow halo around the content view.
- public struct GlowView: ArkView {
- var content: AnyArkView
- var radius: Int
- var color: ArkColor
-
- public func sizeThatFits(proposal: ArkSize) -> ArkSize {
- let size = content.sizeThatFits(proposal: ArkSize(w: max(0, proposal.w - radius * 2), h: max(0, proposal.h - radius * 2)))
- return ArkSize(w: size.w + radius * 2, h: size.h + radius * 2)
- }
-
- public func render(context: ArkUIContext, bounds: ArkRect) {
- // Draw glow as a slightly larger rounded rect behind the content
- context.shapes.fillRoundedRect(x: bounds.x, y: bounds.y, w: bounds.w, h: bounds.h, radius: radius, r: color.r, g: color.g, b: color.b)
-
- let contentBounds = ArkRect(x: bounds.x + radius, y: bounds.y + radius, w: max(0, bounds.w - radius * 2), h: max(0, bounds.h - radius * 2))
- content.render(context: context, bounds: contentBounds)
- }
-
- public func handleInput(event: ArkInputEvent, point: ArkPoint) -> Bool {
- return content.handleInput(event: event, point: point)
- }
- }
- /// Draws a colored outline border around the content view.
- public struct OutlineView: ArkView {
- var content: AnyArkView
- var width: Int
- var color: ArkColor
-
- public func sizeThatFits(proposal: ArkSize) -> ArkSize {
- let size = content.sizeThatFits(proposal: ArkSize(w: max(0, proposal.w - width * 2), h: max(0, proposal.h - width * 2)))
- return ArkSize(w: size.w + width * 2, h: size.h + width * 2)
- }
-
- public func render(context: ArkUIContext, bounds: ArkRect) {
- context.shapes.fillRoundedRect(x: bounds.x, y: bounds.y, w: bounds.w, h: bounds.h, radius: width * 2, r: color.r, g: color.g, b: color.b)
-
- let contentBounds = ArkRect(x: bounds.x + width, y: bounds.y + width, w: max(0, bounds.w - width * 2), h: max(0, bounds.h - width * 2))
- content.render(context: context, bounds: contentBounds)
- }
-
- public func handleInput(event: ArkInputEvent, point: ArkPoint) -> Bool {
- return content.handleInput(event: event, point: point)
- }
- }
- /// Fills a colored background behind the content view with optional corner radius.
- public struct BackgroundView: ArkView {
- var content: AnyArkView
- var color: ArkColor
- var radius: Int = 0
-
- public func sizeThatFits(proposal: ArkSize) -> ArkSize {
- return content.sizeThatFits(proposal: proposal)
- }
-
- public func render(context: ArkUIContext, bounds: ArkRect) {
- context.shapes.fillRoundedRect(x: bounds.x, y: bounds.y, w: bounds.w, h: bounds.h, radius: radius, r: color.r, g: color.g, b: color.b)
- content.render(context: context, bounds: bounds)
- }
-
- public func handleInput(event: ArkInputEvent, point: ArkPoint) -> Bool {
- return content.handleInput(event: event, point: point)
- }
- }
- /// Constrains the view to a specific width and/or height.
- /// Passes the constrained dimensions as the proposal to the child.
- public struct FrameView: ArkView {
- var content: AnyArkView
- var width: Int?
- var height: Int?
- var alignment: ArkAlignment
-
- public func sizeThatFits(proposal: ArkSize) -> ArkSize {
- // Pass constrained dimensions to the child
- let constrainedProposal = ArkSize(w: width ?? proposal.w, h: height ?? proposal.h)
- let cSize = content.sizeThatFits(proposal: constrainedProposal)
- return ArkSize(w: width ?? cSize.w, h: height ?? cSize.h)
- }
-
- public func render(context: ArkUIContext, bounds: ArkRect) {
- let constrainedProposal = ArkSize(w: bounds.w, h: bounds.h)
- let cSize = content.sizeThatFits(proposal: constrainedProposal)
-
- var cx = bounds.x
- var cy = bounds.y
-
- switch alignment {
- case .center:
- cx += (bounds.w - cSize.w) / 2
- cy += (bounds.h - cSize.h) / 2
- case .leading:
- cy += (bounds.h - cSize.h) / 2
- case .trailing:
- cx += bounds.w - cSize.w
- cy += (bounds.h - cSize.h) / 2
- case .top:
- cx += (bounds.w - cSize.w) / 2
- cy += bounds.h - cSize.h
- case .bottom:
- cx += (bounds.w - cSize.w) / 2
- }
-
- content.render(context: context, bounds: ArkRect(x: cx, y: cy, w: cSize.w, h: cSize.h))
- }
-
- public func handleInput(event: ArkInputEvent, point: ArkPoint) -> Bool {
- return content.handleInput(event: event, point: point)
- }
- }
- // MARK: - Modifier Extension Methods
- public extension ArkView {
- /// Adds uniform padding around the view.
- func padding(_ amount: Int = 10) -> AnyArkView {
- return AnyArkView(PaddingView(content: AnyArkView(self), edges: amount))
- }
-
- /// Fills a background color behind the view.
- func background(_ color: ArkColor, cornerRadius: Int = 0) -> AnyArkView {
- return AnyArkView(BackgroundView(content: AnyArkView(self), color: color, radius: cornerRadius))
- }
-
- /// Constrains the view to specific dimensions.
- func frame(width: Int? = nil, height: Int? = nil, alignment: ArkAlignment = .center) -> AnyArkView {
- return AnyArkView(FrameView(content: AnyArkView(self), width: width, height: height, alignment: alignment))
- }
-
- /// Sets the text color (only affects Text views).
- func foregroundColor(_ color: ArkColor) -> AnyArkView {
- if var txt = self as? Text {
- txt.color = color
- return AnyArkView(txt)
- }
- return AnyArkView(self)
- }
-
- /// Sets the font size (only affects Text views).
- func fontSize(_ size: Int) -> AnyArkView {
- if var txt = self as? Text {
- txt.size = size
- return AnyArkView(txt)
- }
- return AnyArkView(self)
- }
-
- /// Adds a colored glow halo around the view.
- func glow(radius: Int, color: ArkColor) -> AnyArkView {
- return AnyArkView(GlowView(content: AnyArkView(self), radius: radius, color: color))
- }
-
- /// Draws a colored outline border around the view.
- func outline(width: Int, color: ArkColor) -> AnyArkView {
- return AnyArkView(OutlineView(content: AnyArkView(self), width: width, color: color))
- }
- }
- // MARK: - Result Builder
- /// Enables declarative view tree construction using Swift result builders.
- @resultBuilder
- public struct ArkViewBuilder {
- public static func buildBlock(_ components: AnyArkView...) -> [AnyArkView] {
- return components
- }
- public static func buildExpression<V: ArkView>(_ expression: V) -> AnyArkView {
- return AnyArkView(expression)
- }
- }
|