///|
/// Border widths for 4 sides independently.
pub(all) struct BorderWidths {
top : Double
right : Double
bottom : Double
left : Double
}
///|
pub fn BorderWidths::uniform(w : Double) -> BorderWidths {
{ top: w, right: w, bottom: w, left: w }
}
///|
pub fn BorderWidths::zero() -> BorderWidths {
{ top: 0.0, right: 0.0, bottom: 0.0, left: 0.0 }
}
///|
pub fn BorderWidths::has_any(self : BorderWidths) -> Bool {
self.top > 0.0 || self.right > 0.0 || self.bottom > 0.0 || self.left > 0.0
}
///|
/// Generate vertices and indices for a 4-side independent border.
/// Pixel coords in, NDC vertices out (for builtin shader).
pub fn border_vertices(
x : Double,
y : Double,
w : Double,
h : Double,
border : BorderWidths,
screen_w : Double,
screen_h : Double,
) -> (Array[Double], Array[Int]) {
let vertices : Array[Double] = []
let indices : Array[Int] = []
let mut base = 0
let ox0 = x
let oy0 = y
let ox1 = x + w
let oy1 = y + h
let ix0 = x + border.left
let iy0 = y + border.top
let ix1 = x + w - border.right
let iy1 = y + h - border.bottom
if border.top > 0.0 {
push_ndc_quad(
vertices, indices, base, ox0, oy0, ox1, oy0, ix1, iy0, ix0, iy0, screen_w,
screen_h,
)
base = base + 4
}
if border.right > 0.0 {
push_ndc_quad(
vertices, indices, base, ox1, oy0, ox1, oy1, ix1, iy1, ix1, iy0, screen_w,
screen_h,
)
base = base + 4
}
if border.bottom > 0.0 {
push_ndc_quad(
vertices, indices, base, ix0, iy1, ix1, iy1, ox1, oy1, ox0, oy1, screen_w,
screen_h,
)
base = base + 4
}
if border.left > 0.0 {
push_ndc_quad(
vertices, indices, base, ox0, oy1, ox0, oy0, ix0, iy0, ix0, iy1, screen_w,
screen_h,
)
base = base + 4
}
ignore(base)
(vertices, indices)
}
///|
/// Generate NDC vertices for a filled rectangle from pixel coords.
pub fn filled_rect_vertices(
x : Double,
y : Double,
w : Double,
h : Double,
screen_w : Double,
screen_h : Double,
) -> (Array[Double], Array[Int]) {
let vertices : Array[Double] = []
let indices : Array[Int] = []
push_ndc_quad(
vertices,
indices,
0,
x,
y,
x + w,
y,
x + w,
y + h,
x,
y + h,
screen_w,
screen_h,
)
(vertices, indices)
}
///|
/// Generate NDC vertices for a filled rounded rectangle.
/// Each corner is approximated with `segments` triangle-fan triangles.
pub fn filled_rounded_rect_vertices(
x : Double,
y : Double,
w : Double,
h : Double,
tl : Double,
tr : Double,
br : Double,
bl : Double,
screen_w : Double,
screen_h : Double,
) -> (Array[Double], Array[Int]) {
let segments = 6 // segments per corner arc
// If all radii are zero, fall back to simple rect
if tl <= 0.0 && tr <= 0.0 && br <= 0.0 && bl <= 0.0 {
return filled_rect_vertices(x, y, w, h, screen_w, screen_h)
}
// Clamp radii to half dimensions
let max_r = @cmp.minimum(w / 2.0, h / 2.0)
let tl = @cmp.minimum(tl, max_r)
let tr = @cmp.minimum(tr, max_r)
let br = @cmp.minimum(br, max_r)
let bl = @cmp.minimum(bl, max_r)
let vertices : Array[Double] = []
let indices : Array[Int] = []
// Center vertex (index 0)
let cx = x + w / 2.0
let cy = y + h / 2.0
vertices.push(px_to_ndc_x(cx, screen_w))
vertices.push(px_to_ndc_y(cy, screen_h))
vertices.push(0.5)
vertices.push(0.5)
let mut idx = 1
// Helper: push a vertex and return its index
fn push_v(px : Double, py : Double) -> Int {
vertices.push(px_to_ndc_x(px, screen_w))
vertices.push(px_to_ndc_y(py, screen_h))
vertices.push(0.0)
vertices.push(0.0)
let i = idx
idx = idx + 1
i
}
// Build perimeter vertices: top-left corner → top-right → bottom-right → bottom-left
let perimeter : Array[Int] = []
// Top-left corner arc (center at x+tl, y+tl, from PI to PI*1.5)
if tl > 0.0 {
for i = 0; i <= segments; i = i + 1 {
let angle = 3.14159265 +
i.to_double() / segments.to_double() * 3.14159265 / 2.0
let px = x + tl + tl * @math.cos(angle)
let py = y + tl + tl * @math.sin(angle)
perimeter.push(push_v(px, py))
}
} else {
perimeter.push(push_v(x, y))
}
// Top-right corner arc (center at x+w-tr, y+tr, from PI*1.5 to PI*2)
if tr > 0.0 {
for i = 0; i <= segments; i = i + 1 {
let angle = 3.14159265 * 1.5 +
i.to_double() / segments.to_double() * 3.14159265 / 2.0
let px = x + w - tr + tr * @math.cos(angle)
let py = y + tr + tr * @math.sin(angle)
perimeter.push(push_v(px, py))
}
} else {
perimeter.push(push_v(x + w, y))
}
// Bottom-right corner arc (center at x+w-br, y+h-br, from 0 to PI*0.5)
if br > 0.0 {
for i = 0; i <= segments; i = i + 1 {
let angle = i.to_double() / segments.to_double() * 3.14159265 / 2.0
let px = x + w - br + br * @math.cos(angle)
let py = y + h - br + br * @math.sin(angle)
perimeter.push(push_v(px, py))
}
} else {
perimeter.push(push_v(x + w, y + h))
}
// Bottom-left corner arc (center at x+bl, y+h-bl, from PI*0.5 to PI)
if bl > 0.0 {
for i = 0; i <= segments; i = i + 1 {
let angle = 3.14159265 / 2.0 +
i.to_double() / segments.to_double() * 3.14159265 / 2.0
let px = x + bl + bl * @math.cos(angle)
let py = y + h - bl + bl * @math.sin(angle)
perimeter.push(push_v(px, py))
}
} else {
perimeter.push(push_v(x, y + h))
}
// Build triangle fan from center (index 0) to perimeter
for i = 0; i < perimeter.length() - 1; i = i + 1 {
indices.push(0)
indices.push(perimeter[i])
indices.push(perimeter[i + 1])
}
// Close the fan
indices.push(0)
indices.push(perimeter[perimeter.length() - 1])
indices.push(perimeter[0])
(vertices, indices)
}
///|
fn px_to_ndc_x(px : Double, screen_w : Double) -> Double {
px / screen_w * 2.0 - 1.0
}
///|
fn px_to_ndc_y(py : Double, screen_h : Double) -> Double {
-(py / screen_h * 2.0 - 1.0)
}
///|
fn push_ndc_quad(
vertices : Array[Double],
indices : Array[Int],
base : Int,
x0 : Double,
y0 : Double,
x1 : Double,
y1 : Double,
x2 : Double,
y2 : Double,
x3 : Double,
y3 : Double,
screen_w : Double,
screen_h : Double,
) -> Unit {
vertices.push(px_to_ndc_x(x0, screen_w))
vertices.push(px_to_ndc_y(y0, screen_h))
vertices.push(0.0)
vertices.push(0.0)
vertices.push(px_to_ndc_x(x1, screen_w))
vertices.push(px_to_ndc_y(y1, screen_h))
vertices.push(1.0)
vertices.push(0.0)
vertices.push(px_to_ndc_x(x2, screen_w))
vertices.push(px_to_ndc_y(y2, screen_h))
vertices.push(1.0)
vertices.push(1.0)
vertices.push(px_to_ndc_x(x3, screen_w))
vertices.push(px_to_ndc_y(y3, screen_h))
vertices.push(0.0)
vertices.push(1.0)
indices.push(base)
indices.push(base + 1)
indices.push(base + 2)
indices.push(base + 2)
indices.push(base + 3)
indices.push(base)
}