Example 02 realized

This commit is contained in:
2026-03-21 23:25:28 -04:00
parent c9966b79ef
commit bc287f615d
6 changed files with 1016 additions and 34 deletions

View File

@@ -43,10 +43,20 @@ struct TextVertex {
#[derive(Clone, Copy, Debug, Default)]
struct ActiveClip {
rect_active: bool,
rect: Option<Rect>,
rounded: Option<(Rect, f32)>,
}
impl ActiveClip {
fn resolved_rect(self) -> Option<Rect> {
if !self.rect_active {
return None;
}
Some(self.rect.unwrap_or_else(empty_clip_rect))
}
}
#[derive(Clone, Copy, Debug)]
struct PixelRect {
left: i32,
@@ -995,8 +1005,20 @@ fn fs_main(input: VertexOut) -> @location(0) vec4<f32> {
let text_bounds_clip = text.bounds.map(|bounds| {
Rect::new(text.origin.x, text.origin.y, bounds.width, bounds.height)
});
let clip_rect =
intersect_rects(active_clip.rect, text_bounds_clip).map(rect_to_pixel_rect);
let logical_clip_rect =
match (active_clip.rect_active, active_clip.rect, text_bounds_clip) {
(true, Some(active_clip_rect), Some(text_bounds_rect)) => {
intersect_rects(Some(active_clip_rect), Some(text_bounds_rect))
}
(true, Some(active_clip_rect), None) => Some(active_clip_rect),
(true, None, _) => None,
(false, _, Some(text_bounds_rect)) => Some(text_bounds_rect),
(false, _, None) => None,
};
if active_clip.rect_active && logical_clip_rect.is_none() {
continue;
}
let clip_rect = logical_clip_rect.map(rect_to_pixel_rect);
for glyph in &text.glyphs {
let Some(cache_key) = glyph.cache_key else {
@@ -1173,7 +1195,7 @@ fn fs_main(input: VertexOut) -> @location(0) vec4<f32> {
.image_cache
.get(&key)
.expect("image cache entry should exist after insertion");
let vertices = build_image_vertices(image.rect, image.uv_rect, logical_size, clip);
let vertices = build_image_vertices(image.rect, image.uv_rect, logical_size, clip)?;
let vertex_buffer = self
.device
.create_buffer_init(&wgpu::util::BufferInitDescriptor {
@@ -1210,7 +1232,7 @@ fn fs_main(input: VertexOut) -> @location(0) vec4<f32> {
text.origin.x + cached.origin_offset.x,
text.origin.y + cached.origin_offset.y,
);
let vertices = build_text_vertices(origin, cached.size, logical_size, clip);
let vertices = build_text_vertices(origin, cached.size, logical_size, clip)?;
let vertex_buffer = self
.device
.create_buffer_init(&wgpu::util::BufferInitDescriptor {
@@ -1635,7 +1657,7 @@ fn build_text_vertices(
size: UiSize,
logical_size: UiSize,
clip: ActiveClip,
) -> [TextVertex; 6] {
) -> Option<[TextVertex; 6]> {
let rect = Rect::new(origin.x, origin.y, size.width, size.height);
build_textured_vertices(
rect,
@@ -1651,7 +1673,7 @@ fn build_image_vertices(
uv_rect: (f32, f32, f32, f32),
logical_size: UiSize,
clip: ActiveClip,
) -> [TextVertex; 6] {
) -> Option<[TextVertex; 6]> {
build_textured_vertices(rect, uv_rect, [1.0, 1.0, 1.0, 1.0], logical_size, clip)
}
@@ -1661,7 +1683,8 @@ fn build_textured_vertices(
color: [f32; 4],
logical_size: UiSize,
clip: ActiveClip,
) -> [TextVertex; 6] {
) -> Option<[TextVertex; 6]> {
let (rect, (u0, v0, u1, v1)) = clip_textured_rect(rect, uv_rect, clip.resolved_rect())?;
let left = to_ndc_x(rect.origin.x, logical_size.width.max(1.0));
let right = to_ndc_x(rect.origin.x + rect.size.width, logical_size.width.max(1.0));
let top = to_ndc_y(rect.origin.y, logical_size.height.max(1.0));
@@ -1669,11 +1692,10 @@ fn build_textured_vertices(
rect.origin.y + rect.size.height,
logical_size.height.max(1.0),
);
let (u0, v0, u1, v1) = uv_rect;
let clip_rect = clip_rect_array(clip.rect);
let clip_rect = clip_rect_array(clip);
let (rounded_clip_rect, clip_params) = rounded_clip_arrays(clip);
[
Some([
TextVertex {
position: [left, top],
world_position: [rect.origin.x, rect.origin.y],
@@ -1731,7 +1753,7 @@ fn build_textured_vertices(
rounded_clip_rect,
clip_params,
},
]
])
}
#[allow(dead_code)]
@@ -1754,7 +1776,7 @@ fn push_glyph_vertices(
let bottom = to_ndc_y(dest_rect.bottom as f32, logical_size.height.max(1.0));
let color = color_to_f32(color);
let clip_rect = clip_rect_array(clip.rect);
let clip_rect = clip_rect_array(clip);
let (rounded_clip_rect, clip_params) = rounded_clip_arrays(clip);
vertices.extend_from_slice(&[
TextVertex {
@@ -1959,7 +1981,7 @@ fn push_shape_vertices(
let rect_data = rect_to_array(shader_rect);
let fill_color = fill_color.map_or([0.0, 0.0, 0.0, 0.0], color_to_f32);
let border_color = border_color.map_or([0.0, 0.0, 0.0, 0.0], color_to_f32);
let clip_rect = clip_rect_array(clip.rect);
let clip_rect = clip_rect_array(clip);
let (rounded_clip_rect, clip_params) = rounded_clip_arrays(clip);
let shadow_base_rect = shadow_base_rect.map_or([0.0, 0.0, 0.0, 0.0], rect_to_array);
@@ -2091,8 +2113,9 @@ fn shadow_blur_extent(blur: f32) -> f32 {
blur.max(0.0) * 2.0
}
fn clip_rect_array(rect: Option<Rect>) -> [f32; 4] {
rect.map_or([0.0, 0.0, 0.0, 0.0], rect_to_array)
fn clip_rect_array(clip: ActiveClip) -> [f32; 4] {
clip.resolved_rect()
.map_or([0.0, 0.0, 0.0, 0.0], rect_to_array)
}
fn rounded_clip_arrays(clip: ActiveClip) -> ([f32; 4], [f32; 2]) {
@@ -2100,7 +2123,7 @@ fn rounded_clip_arrays(clip: ActiveClip) -> ([f32; 4], [f32; 2]) {
.rounded
.map_or([0.0, 0.0, 0.0, 0.0], |(rect, _)| rect_to_array(rect));
let clip_params = [
if clip.rect.is_some() { 1.0 } else { 0.0 },
if clip.rect_active { 1.0 } else { 0.0 },
clip.rounded.map_or(0.0, |(_, radius)| radius),
];
(rounded_clip_rect, clip_params)
@@ -2111,7 +2134,12 @@ fn push_clip_state(stack: &mut Vec<ActiveClip>, active: &mut ActiveClip, region:
if region.radius > 0.0 {
active.rounded = Some((region.rect, region.radius));
}
active.rect = Some(intersect_rects(active.rect, Some(region.rect)).unwrap_or(region.rect));
active.rect = if active.rect_active {
intersect_rects(active.rect, Some(region.rect))
} else {
Some(region.rect)
};
active.rect_active = true;
}
fn pop_clip_state(stack: &mut Vec<ActiveClip>, active: &mut ActiveClip) {
@@ -2136,6 +2164,10 @@ fn intersect_rects(first: Option<Rect>, second: Option<Rect>) -> Option<Rect> {
}
}
fn empty_clip_rect() -> Rect {
Rect::new(1.0, 1.0, -1.0, -1.0)
}
fn to_ndc_x(x: f32, width: f32) -> f32 {
(x / width) * 2.0 - 1.0
}
@@ -2183,6 +2215,43 @@ fn clipped_glyph_quad(
Some((clipped, (u0, v0, u1, v1)))
}
fn clip_textured_rect(
rect: Rect,
uv_rect: (f32, f32, f32, f32),
clip_rect: Option<Rect>,
) -> Option<(Rect, (f32, f32, f32, f32))> {
let clipped = if let Some(clip) = clip_rect {
let left = rect.origin.x.max(clip.origin.x);
let top = rect.origin.y.max(clip.origin.y);
let right = (rect.origin.x + rect.size.width).min(clip.origin.x + clip.size.width);
let bottom = (rect.origin.y + rect.size.height).min(clip.origin.y + clip.size.height);
if right <= left || bottom <= top {
return None;
}
Rect::new(left, top, right - left, bottom - top)
} else {
rect
};
if rect.size.width <= 0.0 || rect.size.height <= 0.0 {
return None;
}
let (u0, v0, u1, v1) = uv_rect;
let clipped_u0 = u0 + (u1 - u0) * ((clipped.origin.x - rect.origin.x) / rect.size.width);
let clipped_v0 = v0 + (v1 - v0) * ((clipped.origin.y - rect.origin.y) / rect.size.height);
let clipped_u1 = u1
- (u1 - u0)
* (((rect.origin.x + rect.size.width) - (clipped.origin.x + clipped.size.width))
/ rect.size.width);
let clipped_v1 = v1
- (v1 - v0)
* (((rect.origin.y + rect.size.height) - (clipped.origin.y + clipped.size.height))
/ rect.size.height);
Some((clipped, (clipped_u0, clipped_v0, clipped_u1, clipped_v1)))
}
fn text_texture_key(text: &PreparedText) -> TextTextureKey {
TextTextureKey {
text: text.text.clone(),
@@ -2208,9 +2277,13 @@ fn text_texture_key(text: &PreparedText) -> TextTextureKey {
#[cfg(test)]
mod tests {
use super::{blend_rgba, build_vertices, text_texture_key};
use super::{
ActiveClip, blend_rgba, build_text_vertices, build_vertices, clip_rect_array,
push_clip_state, rounded_clip_arrays, text_texture_key,
};
use ruin_ui::{
Color, GlyphInstance, Point, PreparedText, Rect, SceneSnapshot, TextSelectionStyle, UiSize,
ClipRegion, Color, GlyphInstance, Point, PreparedText, Rect, SceneSnapshot,
TextSelectionStyle, UiSize,
};
#[test]
@@ -2284,4 +2357,44 @@ mod tests {
assert_eq!(text_texture_key(&first), text_texture_key(&second));
}
#[test]
fn nested_clip_with_empty_intersection_stays_clipped() {
let mut clip_stack = Vec::new();
let mut active_clip = ActiveClip::default();
push_clip_state(
&mut clip_stack,
&mut active_clip,
ClipRegion {
rect: Rect::new(0.0, 0.0, 100.0, 100.0),
radius: 0.0,
},
);
push_clip_state(
&mut clip_stack,
&mut active_clip,
ClipRegion {
rect: Rect::new(150.0, 150.0, 50.0, 50.0),
radius: 8.0,
},
);
assert!(active_clip.rect_active);
assert_eq!(active_clip.rect, None);
let clip_rect = clip_rect_array(active_clip);
assert!(clip_rect[0] > clip_rect[2]);
assert!(clip_rect[1] > clip_rect[3]);
let (_, clip_params) = rounded_clip_arrays(active_clip);
assert_eq!(clip_params[0], 1.0);
assert!(
build_text_vertices(
Point::new(160.0, 160.0),
UiSize::new(24.0, 12.0),
UiSize::new(400.0, 400.0),
active_clip,
)
.is_none()
);
}
}