1#define_import_path bevy_ui::ui_node3#import bevy_render::view::View5const TEXTURED = 1u;6const RIGHT_VERTEX = 2u;7const BOTTOM_VERTEX = 4u;8// must align with BORDER_* shader_flags from bevy_ui/render/mod.rs9const BORDER_LEFT: u32 = 256u;10const BORDER_TOP: u32 = 512u;11const BORDER_RIGHT: u32 = 1024u;12const BORDER_BOTTOM: u32 = 2048u;13const BORDER_ANY: u32 = BORDER_LEFT + BORDER_TOP + BORDER_RIGHT + BORDER_BOTTOM;14const INVERT: u32 = 4096u;16fn enabled(flags: u32, mask: u32) -> bool {17 return (flags & mask) != 0u;18}20@group(0) @binding(0) var<uniform> view: View;22struct VertexOutput {23 @location(0) uv: vec2<f32>,24 @location(1) color: vec4<f32>,26 @location(2) @interpolate(flat) size: vec2<f32>,27 @location(3) @interpolate(flat) flags: u32,28 @location(4) @interpolate(flat) radius: vec4<f32>, 29 @location(5) @interpolate(flat) border: vec4<f32>, 31 // Position relative to the center of the rectangle.32 @location(6) point: vec2<f32>,33 @builtin(position) position: vec4<f32>,34};36@vertex37fn vertex(38 @location(0) vertex_position: vec3<f32>,39 @location(1) vertex_uv: vec2<f32>,40 @location(2) vertex_color: vec4<f32>,41 @location(3) flags: u32,43 // x: top left, y: top right, z: bottom right, w: bottom left.44 @location(4) radius: vec4<f32>,46 // x: left, y: top, z: right, w: bottom.47 @location(5) border: vec4<f32>,48 @location(6) size: vec2<f32>,49 @location(7) point: vec2<f32>,50) -> VertexOutput {51 var out: VertexOutput;52 out.uv = vertex_uv;53 out.position = view.clip_from_world * vec4(vertex_position, 1.0);54 out.color = vertex_color;55 out.flags = flags;56 out.radius = radius;57 out.size = size;58 out.border = border;59 out.point = point;61 return out;62}64@group(1) @binding(0) var sprite_texture: texture_2d<f32>;65@group(1) @binding(1) var sprite_sampler: sampler;67// The returned value is the shortest distance from the given point to the boundary of the rounded 68// box.69// 70// Negative values indicate that the point is inside the rounded box, positive values that the point 71// is outside, and zero is exactly on the boundary.72//73// Arguments: 74// - `point` -> The function will return the distance from this point to the closest point on 75// the boundary.76// - `size` -> The maximum width and height of the box.77// - `corner_radii` -> The radius of each rounded corner. Ordered counter clockwise starting 78// top left:79// x: top left, y: top right, z: bottom right, w: bottom left.80fn sd_rounded_box(point: vec2<f32>, size: vec2<f32>, corner_radii: vec4<f32>) -> f32 {81 // If 0.0 < y then select bottom left (w) and bottom right corner radius (z).82 // Else select top left (x) and top right corner radius (y).83 let rs = select(corner_radii.xy, corner_radii.wz, 0.0 < point.y);84 // w and z are swapped above so that both pairs are in left to right order, otherwise this second 85 // select statement would return the incorrect value for the bottom pair.86 let radius = select(rs.x, rs.y, 0.0 < point.x);87 // Vector from the corner closest to the point, to the point.88 let corner_to_point = abs(point) - 0.5 * size;89 // Vector from the center of the radius circle to the point.90 let q = corner_to_point + radius;91 // Length from center of the radius circle to the point, zeros a component if the point is not 92 // within the quadrant of the radius circle that is part of the curved corner.93 let l = length(max(q, vec2(0.0)));94 let m = min(max(q.x, q.y), 0.0);95 return l + m - radius;96}98fn sd_inset_rounded_box(point: vec2<f32>, size: vec2<f32>, radius: vec4<f32>, inset: vec4<f32>) -> f32 {99 let inner_size = size - inset.xy - inset.zw;100 let inner_center = inset.xy + 0.5 * inner_size - 0.5 * size;101 let inner_point = point - inner_center;103 var r = radius;105 // Top left corner.106 r.x = r.x - max(inset.x, inset.y);108 // Top right corner.109 r.y = r.y - max(inset.z, inset.y);111 // Bottom right corner.112 r.z = r.z - max(inset.z, inset.w); 114 // Bottom left corner.115 r.w = r.w - max(inset.x, inset.w);117 let half_size = inner_size * 0.5;118 let min_size = min(half_size.x, half_size.y);120 r = min(max(r, vec4(0.0)), vec4<f32>(min_size));122 return sd_rounded_box(inner_point, inner_size, r);123}125fn nearest_border_active(point_vs_mid: vec2<f32>, size: vec2<f32>, width: vec4<f32>, flags: u32) -> bool {126 if (flags & BORDER_ANY) == BORDER_ANY {127 return true;128 }130 // get point vs top left131 let point = clamp(point_vs_mid + size * 0.49999, vec2(0.0), size);133 let left = point.x / width.x;134 let top = point.y / width.y;135 let right = (size.x - point.x) / width.z;136 let bottom = (size.y - point.y) / width.w;138 let min_dist = min(min(left, top), min(right, bottom));140 return (enabled(flags, BORDER_LEFT) && min_dist == left) ||141 (enabled(flags, BORDER_TOP) && min_dist == top) || 142 (enabled(flags, BORDER_RIGHT) && min_dist == right) || 143 (enabled(flags, BORDER_BOTTOM) && min_dist == bottom);144}146// get alpha for antialiasing for sdf147fn antialias(distance: f32) -> f32 {148 // Using the fwidth(distance) was causing artifacts, so just use the distance.149 return saturate(0.5 - distance);150}152fn draw_uinode_border(153 color: vec4<f32>,154 point: vec2<f32>,155 size: vec2<f32>,156 radius: vec4<f32>,157 border: vec4<f32>,158 flags: u32,159) -> vec4<f32> {160 // Signed distances. The magnitude is the distance of the point from the edge of the shape.161 // * Negative values indicate that the point is inside the shape.162 // * Zero values indicate the point is on the edge of the shape.163 // * Positive values indicate the point is outside the shape.165 // Signed distance from the exterior boundary.166 let external_distance = sd_rounded_box(point, size, radius);168 // Signed distance from the border's internal edge (the signed distance is negative if the point 169 // is inside the rect but not on the border).170 // If the border size is set to zero, this is the same as the external distance.171 let internal_distance = sd_inset_rounded_box(point, size, radius, border);173 // Signed distance from the border (the intersection of the rect with its border).174 // Points inside the border have negative signed distance. Any point outside the border, whether 175 // outside the outside edge, or inside the inner edge have positive signed distance.176 let border_distance = max(external_distance, -internal_distance);178 // check if this node should apply color for the nearest border179 let nearest_border = select(0.0, 1.0, nearest_border_active(point, size, border, flags));181#ifdef ANTI_ALIAS182 // At external edges with no border, `border_distance` is equal to zero. 183 // This select statement ensures we only perform anti-aliasing where a non-zero width border 184 // is present, otherwise an outline about the external boundary would be drawn even without 185 // a border.186 let t = select(1.0 - step(0.0, border_distance), antialias(border_distance), external_distance < internal_distance);187#else188 let t = 1.0 - step(0.0, border_distance);189#endif191 // Blend mode ALPHA_BLENDING is used for UI elements, so we don't premultiply alpha here.192 return vec4(color.rgb, saturate(color.a * t * nearest_border));193}195fn draw_uinode_background(196 color: vec4<f32>,197 point: vec2<f32>,198 size: vec2<f32>,199 radius: vec4<f32>,200 border: vec4<f32>,201 flags: u32,202) -> vec4<f32> {203 // When drawing the background only draw the internal area and not the border.204 let internal_distance = sd_inset_rounded_box(point, size, radius, border) * select(1., -1, enabled(flags, INVERT));206#ifdef ANTI_ALIAS207 let t = antialias(internal_distance);208#else209 let t = 1.0 - step(0.0, internal_distance);210#endif212 return vec4(color.rgb, saturate(color.a * t));213}215@fragment216fn fragment(in: VertexOutput) -> @location(0) vec4<f32> {217 let texture_color = textureSample(sprite_texture, sprite_sampler, in.uv);219 // Only use the color sampled from the texture if the `TEXTURED` flag is enabled. 220 // This allows us to draw both textured and untextured shapes together in the same batch.221 let color = select(in.color, in.color * texture_color, enabled(in.flags, TEXTURED));223 if enabled(in.flags, BORDER_ANY) {224 return draw_uinode_border(color, in.point, in.size, in.radius, in.border, in.flags);225 } else {226 return draw_uinode_background(color, in.point, in.size, in.radius, in.border, in.flags);227 }228}