///|
fn transparent(color : @render.Color) -> @render.Color {
{ ..color, a: 0.0 }
}
///|
fn scaled_alpha(color : @render.Color, alpha : Double) -> @render.Color {
{ ..color, a: alpha.clamp(min=0.0, max=color.a) }
}
///|
fn safe_radii(
rect : @math.Rect,
radii : RoundedCornerRadii2D,
) -> RoundedCornerRadii2D {
let tl = radii.top_left.clamp(min=0.0, max=1.0e300)
let tr = radii.top_right.clamp(min=0.0, max=1.0e300)
let br = radii.bottom_right.clamp(min=0.0, max=1.0e300)
let bl = radii.bottom_left.clamp(min=0.0, max=1.0e300)
let width = rect.size[X].clamp(min=0.0, max=1.0e300)
let height = rect.size[Y].clamp(min=0.0, max=1.0e300)
let mut scale = 1.0
if tl + tr > 0.0 {
scale = @cmp.minimum(scale, width / (tl + tr))
}
if bl + br > 0.0 {
scale = @cmp.minimum(scale, width / (bl + br))
}
if tl + bl > 0.0 {
scale = @cmp.minimum(scale, height / (tl + bl))
}
if tr + br > 0.0 {
scale = @cmp.minimum(scale, height / (tr + br))
}
{
top_left: tl * scale,
top_right: tr * scale,
bottom_right: br * scale,
bottom_left: bl * scale,
}
}
///|
fn maximum_radius(radii : RoundedCornerRadii2D) -> Double {
@cmp.maximum(
@cmp.maximum(radii.top_left, radii.top_right),
@cmp.maximum(radii.bottom_right, radii.bottom_left),
)
}
///|
fn safe_edge_widths(rect : @math.Rect, edges : EdgeWidths2D) -> EdgeWidths2D {
let left = edges.left.clamp(min=0.0, max=1.0e300)
let right = edges.right.clamp(min=0.0, max=1.0e300)
let top = edges.top.clamp(min=0.0, max=1.0e300)
let bottom = edges.bottom.clamp(min=0.0, max=1.0e300)
let width = rect.size[X].clamp(min=0.0, max=1.0e300)
let height = rect.size[Y].clamp(min=0.0, max=1.0e300)
let horizontal_scale = if left + right > width && left + right > 0.0 {
width / (left + right)
} else {
1.0
}
let vertical_scale = if top + bottom > height && top + bottom > 0.0 {
height / (top + bottom)
} else {
1.0
}
{
top: top * vertical_scale,
right: right * horizontal_scale,
bottom: bottom * vertical_scale,
left: left * horizontal_scale,
}
}
///|
fn inset_rect_edges(rect : @math.Rect, edges : EdgeWidths2D) -> @math.Rect {
{
position: @math.Vec2(
rect.position[X] + edges.left,
rect.position[Y] + edges.top,
),
size: @math.Vec2(
(rect.size[X] - edges.left - edges.right).clamp(min=0.0, max=1.0e300),
(rect.size[Y] - edges.top - edges.bottom).clamp(min=0.0, max=1.0e300),
),
}
}
///|
fn inset_radii_edges(
radii : RoundedCornerRadii2D,
edges : EdgeWidths2D,
) -> RoundedCornerRadii2D {
{
top_left: (radii.top_left - @cmp.maximum(edges.top, edges.left)).clamp(
min=0.0,
max=1.0e300,
),
top_right: (radii.top_right - @cmp.maximum(edges.top, edges.right)).clamp(
min=0.0,
max=1.0e300,
),
bottom_right: (radii.bottom_right - @cmp.maximum(edges.bottom, edges.right)).clamp(
min=0.0,
max=1.0e300,
),
bottom_left: (radii.bottom_left - @cmp.maximum(edges.bottom, edges.left)).clamp(
min=0.0,
max=1.0e300,
),
}
}
///|
fn inflate_rect_uniform(rect : @math.Rect, amount : Double) -> @math.Rect {
{
position: @math.Vec2(rect.position[X] - amount, rect.position[Y] - amount),
size: @math.Vec2(
(rect.size[X] + amount * 2.0).clamp(min=0.0, max=1.0e300),
(rect.size[Y] + amount * 2.0).clamp(min=0.0, max=1.0e300),
),
}
}
///|
fn offset_rect_by(rect : @math.Rect, offset : @math.Vec2) -> @math.Rect {
{ position: rect.position + offset, size: rect.size }
}
///|
fn inflate_radii_uniform(
radii : RoundedCornerRadii2D,
amount : Double,
) -> RoundedCornerRadii2D {
{
top_left: (radii.top_left + amount).clamp(min=0.0, max=1.0e300),
top_right: (radii.top_right + amount).clamp(min=0.0, max=1.0e300),
bottom_right: (radii.bottom_right + amount).clamp(min=0.0, max=1.0e300),
bottom_left: (radii.bottom_left + amount).clamp(min=0.0, max=1.0e300),
}
}
///|
fn corner_segments(radius : Double, output_scale : Double) -> Int {
if radius <= 0.0 {
1
} else {
// Keep the maximum chord below roughly one physical pixel while bounding
// command expansion for very large panels.
let circumference = radius *
output_scale.clamp(min=1.0, max=1.0e300) *
@cmath.PI /
2.0
circumference.ceil().to_int().clamp(min=4, max=32)
}
}
///|
fn push_corner_points(
points : Array[@math.Vec2],
center : @math.Vec2,
radius : Double,
start_angle : Double,
output_scale : Double,
) -> Unit {
let segments = corner_segments(radius, output_scale)
for index in 0..<=segments {
let angle = start_angle +
index.to_double() / segments.to_double() * @cmath.PI / 2.0
points.push(
@math.Vec2(
center[X] + @cmath.cos(angle) * radius,
center[Y] + @cmath.sin(angle) * radius,
),
)
}
}
///|
fn rounded_perimeter(
rect : @math.Rect,
radii : RoundedCornerRadii2D,
output_scale : Double,
) -> Array[@math.Vec2] {
let radii = safe_radii(rect, radii)
let x = rect.position[X]
let y = rect.position[Y]
let w = rect.size[X]
let h = rect.size[Y]
let points : Array[@math.Vec2] = []
push_corner_points(
points,
@math.Vec2(x + w - radii.top_right, y + radii.top_right),
radii.top_right,
-@cmath.PI / 2.0,
output_scale,
)
push_corner_points(
points,
@math.Vec2(x + w - radii.bottom_right, y + h - radii.bottom_right),
radii.bottom_right,
0.0,
output_scale,
)
push_corner_points(
points,
@math.Vec2(x + radii.bottom_left, y + h - radii.bottom_left),
radii.bottom_left,
@cmath.PI / 2.0,
output_scale,
)
push_corner_points(
points,
@math.Vec2(x + radii.top_left, y + radii.top_left),
radii.top_left,
@cmath.PI,
output_scale,
)
points
}
///|
fn vertex(position : @math.Vec2, color : @render.Color) -> ColoredVertex2D {
{ position, color }
}
///|
fn push_triangle(
triangles : Array[ColoredTriangle2D],
a : @math.Vec2,
b : @math.Vec2,
c : @math.Vec2,
ca : @render.Color,
cb : @render.Color,
cc : @render.Color,
) -> Unit {
triangles.push({ a: vertex(a, ca), b: vertex(b, cb), c: vertex(c, cc) })
}
///|
fn push_filled_shape(
triangles : Array[ColoredTriangle2D],
rect : @math.Rect,
radii : RoundedCornerRadii2D,
color : @render.Color,
output_scale : Double,
) -> Unit {
guard rect.size[X] > 0.0 && rect.size[Y] > 0.0 && color.a > 0.0 else {
return
}
let perimeter = rounded_perimeter(rect, radii, output_scale)
let center = @math.Vec2(
rect.position[X] + rect.size[X] / 2.0,
rect.position[Y] + rect.size[Y] / 2.0,
)
for index in 0.. Unit {
guard outer_rect.size[X] > 0.0 && outer_rect.size[Y] > 0.0 && color.a > 0.0 else {
return
}
// Identical segment counts preserve correspondence for arbitrary per-edge
// insets while still refining curves for physical output scale.
let segments = corner_segments(
@cmp.maximum(maximum_radius(outer_radii), maximum_radius(inner_radii)),
output_scale,
)
let outer = rounded_perimeter_fixed(outer_rect, outer_radii, segments)
let inner = rounded_perimeter_fixed(inner_rect, inner_radii, segments)
for index in 0.. Unit {
guard outer_rect.size[X] > 0.0 && outer_rect.size[Y] > 0.0 else { return }
let segments = corner_segments(
@cmp.maximum(maximum_radius(outer_radii), maximum_radius(inner_radii)),
output_scale,
)
let outer = rounded_perimeter_fixed(outer_rect, outer_radii, segments)
let inner = rounded_perimeter_fixed(inner_rect, inner_radii, segments)
for index in 0.. Array[@math.Vec2] {
let radii = safe_radii(rect, radii)
let x = rect.position[X]
let y = rect.position[Y]
let w = rect.size[X]
let h = rect.size[Y]
let points : Array[@math.Vec2] = []
let centers = [
(
@math.Vec2(x + w - radii.top_right, y + radii.top_right),
radii.top_right,
-@cmath.PI / 2.0,
),
(
@math.Vec2(x + w - radii.bottom_right, y + h - radii.bottom_right),
radii.bottom_right,
0.0,
),
(
@math.Vec2(x + radii.bottom_left, y + h - radii.bottom_left),
radii.bottom_left,
@cmath.PI / 2.0,
),
(
@math.Vec2(x + radii.top_left, y + radii.top_left),
radii.top_left,
@cmath.PI,
),
]
for corner in centers {
for index in 0..<=segments {
let angle = corner.2 +
index.to_double() / segments.to_double() * @cmath.PI / 2.0
points.push(
@math.Vec2(
corner.0[X] + @cmath.cos(angle) * corner.1,
corner.0[Y] + @cmath.sin(angle) * corner.1,
),
)
}
}
points
}
///|
fn push_outer_fringe(
triangles : Array[ColoredTriangle2D],
rect : @math.Rect,
radii : RoundedCornerRadii2D,
color : @render.Color,
output_scale : Double,
) -> Unit {
let aa = 1.0 / output_scale.clamp(min=1.0, max=1.0e300)
let outer_rect = inflate_rect_uniform(rect, aa)
let outer_radii = inflate_radii_uniform(radii, aa)
push_gradient_ring(
triangles,
outer_rect,
outer_radii,
rect,
radii,
transparent(color),
color,
output_scale,
)
}
///|
fn push_inner_fringe(
triangles : Array[ColoredTriangle2D],
rect : @math.Rect,
radii : RoundedCornerRadii2D,
color : @render.Color,
output_scale : Double,
) -> Unit {
let aa = 1.0 / output_scale.clamp(min=1.0, max=1.0e300)
let inset = EdgeWidths2D::{ top: aa, right: aa, bottom: aa, left: aa }
push_gradient_ring(
triangles,
rect,
radii,
inset_rect_edges(rect, inset),
inset_radii_edges(radii, inset),
color,
transparent(color),
output_scale,
)
}
///|
fn push_shadow(
triangles : Array[ColoredTriangle2D],
base_rect : @math.Rect,
base_radii : RoundedCornerRadii2D,
shadow : RoundedShadowLayer2D,
output_scale : Double,
) -> Unit {
guard shadow.color.a > 0.0 else { return }
let spread = shadow.spread
let core_rect = offset_rect_by(
inflate_rect_uniform(base_rect, spread),
shadow.offset,
)
let core_radii = inflate_radii_uniform(base_radii, spread)
let blur = shadow.blur.clamp(min=0.0, max=1.0e300)
if blur <= 0.0 {
push_filled_shape(
triangles,
core_rect,
core_radii,
shadow.color,
output_scale,
)
push_outer_fringe(
triangles,
core_rect,
core_radii,
shadow.color,
output_scale,
)
return
}
let steps = (blur * output_scale.clamp(min=1.0, max=1.0e300))
.ceil()
.to_int()
.clamp(min=8, max=64)
// Adjacent rings interpolate the smoothstep coverage continuously, avoiding
// the sampled solid bands used by the old composite implementation.
for index in 0.. Array[ColoredTriangle2D] {
let triangles : Array[ColoredTriangle2D] = []
let scale = command.output_scale.clamp(min=1.0, max=1.0e300)
let radii = safe_radii(command.rect, command.radii)
for shadow in command.shadows {
push_shadow(triangles, command.rect, radii, shadow, scale)
}
match command.fill_color {
Some(fill) => {
push_filled_shape(triangles, command.rect, radii, fill, scale)
if command.border_color is None {
push_outer_fringe(triangles, command.rect, radii, fill, scale)
}
}
None => ()
}
match command.border_color {
Some(border) => {
let border_widths = safe_edge_widths(command.rect, command.border_widths)
let inner_rect = inset_rect_edges(command.rect, border_widths)
let inner_radii = inset_radii_edges(radii, border_widths)
push_ring(
triangles,
command.rect,
radii,
inner_rect,
inner_radii,
border,
scale,
)
push_outer_fringe(triangles, command.rect, radii, border, scale)
if command.fill_color is None {
push_inner_fringe(triangles, inner_rect, inner_radii, border, scale)
}
}
None => ()
}
match command.outline {
Some(outline) if outline.width > 0.0 && outline.color.a > 0.0 => {
let inner_rect = inflate_rect_uniform(command.rect, outline.offset)
let inner_radii = inflate_radii_uniform(radii, outline.offset)
let outer_rect = inflate_rect_uniform(
command.rect,
outline.offset + outline.width,
)
let outer_radii = inflate_radii_uniform(
radii,
outline.offset + outline.width,
)
push_ring(
triangles,
outer_rect,
outer_radii,
inner_rect,
inner_radii,
outline.color,
scale,
)
push_outer_fringe(
triangles,
outer_rect,
outer_radii,
outline.color,
scale,
)
push_inner_fringe(
triangles,
inner_rect,
inner_radii,
outline.color,
scale,
)
}
_ => ()
}
triangles
}