waterui 0.3.0

A modern UI framework for Rust
import CWaterUI
import CoreGraphics

enum WuiShapePath {
    static func commands(from array: WuiArray_WuiPathCommand) -> [WuiPathCommand] {
        let slice = array.vtable.slice(array.data)
        guard let head = slice.head else {
            return []
        }
        return Array(UnsafeBufferPointer(start: head, count: Int(slice.len)))
    }

    /// Builds the shape's path from its structured kind, in point space.
    ///
    /// The command list is in unit space, so a corner drawn from it stretches
    /// with the rect's aspect ratio — a rounded rectangle wider than tall gets
    /// flat elliptical corners. The kind carries what the commands cannot: a
    /// corner radius as a fraction of the *shorter* side, applied uniformly in
    /// points. Only a custom path falls back to the unit-space commands.
    static func makePath(
        kind: WuiShapeKind, commands: [WuiPathCommand], in bounds: CGRect
    ) -> CGPath {
        let shorter = min(bounds.width, bounds.height)
        switch kind.tag {
        case 0:  // Rect
            return CGPath(rect: bounds, transform: nil)
        case 1:  // Circle: centered, diameter is the shorter side
            let rect = CGRect(
                x: bounds.midX - shorter / 2,
                y: bounds.midY - shorter / 2,
                width: shorter,
                height: shorter
            )
            return CGPath(ellipseIn: rect, transform: nil)
        case 2:  // Ellipse
            return CGPath(ellipseIn: bounds, transform: nil)
        case 3:  // Rounded rect, uniform normalized radius
            let radius = min(CGFloat(kind.top_left) * shorter, shorter / 2)
            return CGPath(
                roundedRect: bounds, cornerWidth: radius, cornerHeight: radius, transform: nil)
        case 4:  // Uneven rounded rect, per-corner normalized radii
            return unevenRoundedRect(kind: kind, in: bounds)
        case 5:  // Capsule
            let radius = shorter / 2
            return CGPath(
                roundedRect: bounds, cornerWidth: radius, cornerHeight: radius, transform: nil)
        case 6:  // Custom path: only the commands describe it
            return makePath(commands: commands, in: bounds)
        default:
            fatalError("unknown WaterUI shape kind tag \(kind.tag)")
        }
    }

    private static func unevenRoundedRect(kind: WuiShapeKind, in bounds: CGRect) -> CGPath {
        let shorter = min(bounds.width, bounds.height)
        let limit = shorter / 2
        let tl = min(CGFloat(kind.top_left) * shorter, limit)
        let tr = min(CGFloat(kind.top_right) * shorter, limit)
        let br = min(CGFloat(kind.bottom_right) * shorter, limit)
        let bl = min(CGFloat(kind.bottom_left) * shorter, limit)

        let path = CGMutablePath()
        path.move(to: CGPoint(x: bounds.minX + tl, y: bounds.minY))
        path.addArc(
            tangent1End: CGPoint(x: bounds.maxX, y: bounds.minY),
            tangent2End: CGPoint(x: bounds.maxX, y: bounds.maxY), radius: tr)
        path.addArc(
            tangent1End: CGPoint(x: bounds.maxX, y: bounds.maxY),
            tangent2End: CGPoint(x: bounds.minX, y: bounds.maxY), radius: br)
        path.addArc(
            tangent1End: CGPoint(x: bounds.minX, y: bounds.maxY),
            tangent2End: CGPoint(x: bounds.minX, y: bounds.minY), radius: bl)
        path.addArc(
            tangent1End: CGPoint(x: bounds.minX, y: bounds.minY),
            tangent2End: CGPoint(x: bounds.maxX, y: bounds.minY), radius: tl)
        path.closeSubpath()
        return path
    }

    static func makePath(commands: [WuiPathCommand], in bounds: CGRect) -> CGPath {
        let path = CGMutablePath()
        let width = bounds.width
        let height = bounds.height

        for command in commands {
            switch command.tag {
            case WuiPathCommand_MoveTo:
                path.move(to: denormalize(command.move_to.x, command.move_to.y, width: width, height: height))

            case WuiPathCommand_LineTo:
                path.addLine(to: denormalize(command.line_to.x, command.line_to.y, width: width, height: height))

            case WuiPathCommand_QuadTo:
                path.addQuadCurve(
                    to: denormalize(command.quad_to.x, command.quad_to.y, width: width, height: height),
                    control: denormalize(command.quad_to.cx, command.quad_to.cy, width: width, height: height)
                )

            case WuiPathCommand_CubicTo:
                path.addCurve(
                    to: denormalize(command.cubic_to.x, command.cubic_to.y, width: width, height: height),
                    control1: denormalize(command.cubic_to.c1x, command.cubic_to.c1y, width: width, height: height),
                    control2: denormalize(command.cubic_to.c2x, command.cubic_to.c2y, width: width, height: height)
                )

            case WuiPathCommand_Arc:
                addArc(to: path, arc: command.arc, width: width, height: height)

            case WuiPathCommand_Close:
                path.closeSubpath()

            default:
                fatalError("unknown WaterUI path command tag \(command.tag)")
            }
        }

        return path
    }

    private static func denormalize(_ x: Float, _ y: Float, width: CGFloat, height: CGFloat) -> CGPoint {
        CGPoint(x: CGFloat(x) * width, y: CGFloat(y) * height)
    }

    private static func addArc(
        to path: CGMutablePath,
        arc: WuiPathCommand_Arc_Body,
        width: CGFloat,
        height: CGFloat
    ) {
        let cx = CGFloat(arc.cx) * width
        let cy = CGFloat(arc.cy) * height
        let rx = CGFloat(arc.rx) * width
        let ry = CGFloat(arc.ry) * height
        let start = CGFloat(arc.start)
        let sweep = CGFloat(arc.sweep)

        if abs(sweep) >= .pi * 2.0 - 0.0001 {
            path.addEllipse(in: CGRect(x: cx - rx, y: cy - ry, width: rx * 2.0, height: ry * 2.0))
            return
        }

        var transform = CGAffineTransform(translationX: cx, y: cy)
        transform = transform.scaledBy(x: rx, y: ry)
        path.addArc(
            center: .zero,
            radius: 1.0,
            startAngle: start,
            endAngle: start + sweep,
            clockwise: sweep < 0.0,
            transform: transform
        )
    }
}