diff --git a/crates/yakui-widgets/src/shapes.rs b/crates/yakui-widgets/src/shapes.rs index 9c226e57..36eb1d47 100644 --- a/crates/yakui-widgets/src/shapes.rs +++ b/crates/yakui-widgets/src/shapes.rs @@ -2,6 +2,7 @@ use std::f32::consts::TAU; use yakui_core::geometry::{Color, Rect, Vec2}; use yakui_core::paint::{PaintDom, PaintMesh, PaintRect, Vertex}; +use yakui_core::TextureId; pub fn cross(output: &mut PaintDom, rect: Rect, color: Color) { static POSITIONS: [[f32; 2]; 12] = [ @@ -167,6 +168,7 @@ pub struct RoundedRectangle { pub rect: Rect, pub radius: f32, pub color: Color, + pub texture: Option<(TextureId, Rect)>, } impl RoundedRectangle { @@ -175,16 +177,12 @@ impl RoundedRectangle { rect, radius, color: Color::WHITE, + texture: None, } } pub fn add(&self, output: &mut PaintDom) { - if self.radius <= 0.0 { - return PaintRect::new(self.rect).add(output); - } - let rect = self.rect; - let color = self.color.to_linear(); // We are not prepared to let a corner's radius be bigger than a side's // half-length. @@ -193,14 +191,35 @@ impl RoundedRectangle { .min(rect.size().x / 2.0) .min(rect.size().y / 2.0); - let slices = if radius >= 1.0 { - f32::ceil(TAU / 8.0 / f32::acos(1.0 - 0.2 / radius)) as u32 - } else { - 1 + // Fallback to a rectangle if the radius is too small. + if radius < 1.0 { + let mut p = PaintRect::new(rect); + p.texture = self.texture; + p.color = self.color; + return p.add(output); + } + + let color = self.color.to_linear(); + + let slices = f32::ceil(TAU / 8.0 / f32::acos(1.0 - 0.2 / radius)) as u32; + + // 3 rectangles and 4 corners + let mut vertices = Vec::with_capacity(4 * 3 + (slices + 2) as usize * 4); + let mut indices = Vec::with_capacity(6 * 3 + slices as usize * (3 * 4)); + + let (uv_offset, uv_factor) = self + .texture + .map(|(_, texture_rect)| (texture_rect.pos(), texture_rect.size() / rect.size())) + .unwrap_or((Vec2::ZERO, Vec2::ZERO)); + + let calc_uv = |position| { + if self.texture.is_none() { + return Vec2::ZERO; + } + (position - rect.pos()) * uv_factor + uv_offset }; - let mut vertices = Vec::new(); - let mut indices = Vec::new(); + let create_vertex = |pos| Vertex::new(pos, calc_uv(pos), color); let mut rectangle = |min: Vec2, max: Vec2| { let base_vertex = vertices.len(); @@ -208,7 +227,7 @@ impl RoundedRectangle { let size = max - min; let rect_vertices = RECT_POS .map(Vec2::from) - .map(|vert| Vertex::new(vert * size + min, Vec2::ZERO, color)); + .map(|vert| create_vertex(vert * size + min)); let rect_indices = RECT_INDEX.map(|index| index + base_vertex as u16); @@ -231,17 +250,17 @@ impl RoundedRectangle { let mut corner = |center: Vec2, start_angle: f32| { let center_vertex = vertices.len(); - vertices.push(Vertex::new(center, Vec2::ZERO, color)); + vertices.push(create_vertex(center)); let first_offset = radius * Vec2::new(start_angle.cos(), -start_angle.sin()); - vertices.push(Vertex::new(center + first_offset, Vec2::ZERO, color)); + vertices.push(create_vertex(center + first_offset)); for i in 1..=slices { let percent = i as f32 / slices as f32; let angle = start_angle + percent * TAU / 4.0; let offset = radius * Vec2::new(angle.cos(), -angle.sin()); let index = vertices.len(); - vertices.push(Vertex::new(center + offset, Vec2::ZERO, color)); + vertices.push(create_vertex(center + offset)); indices.extend_from_slice(&[ center_vertex as u16, @@ -265,7 +284,8 @@ impl RoundedRectangle { 3.0 * TAU / 4.0, ); - let mesh = PaintMesh::new(vertices, indices); + let mut mesh = PaintMesh::new(vertices, indices); + mesh.texture = self.texture; output.add_mesh(mesh); } } diff --git a/crates/yakui-widgets/src/widgets/cutout.rs b/crates/yakui-widgets/src/widgets/cutout.rs index f6ae412c..11b28b0e 100644 --- a/crates/yakui-widgets/src/widgets/cutout.rs +++ b/crates/yakui-widgets/src/widgets/cutout.rs @@ -1,5 +1,5 @@ +use crate::shapes::RoundedRectangle; use yakui_core::geometry::{Color, Constraints, Rect, Vec2}; -use yakui_core::paint::PaintRect; use yakui_core::widget::{LayoutContext, PaintContext, Widget}; use yakui_core::{Response, TextureId}; @@ -18,6 +18,7 @@ pub struct CutOut { pub image_color: Color, pub overlay_color: Color, pub min_size: Vec2, + pub radius: f32, } impl CutOut { @@ -30,6 +31,7 @@ impl CutOut { image_color: Color::WHITE, overlay_color, min_size: Vec2::ZERO, + radius: 0.0, } } @@ -60,6 +62,7 @@ impl Widget for CutOutWidget { image_color: Color::WHITE, overlay_color: Color::CLEAR, min_size: Vec2::ZERO, + radius: 0.0, }, } } @@ -91,13 +94,13 @@ impl Widget for CutOutWidget { layout_node.rect.size() / ctx.layout.viewport().size(), ); - let mut rect = PaintRect::new(layout_node.rect); + let mut rect = RoundedRectangle::new(layout_node.rect, self.props.radius); rect.color = self.props.image_color; rect.texture = Some((image, texture_rect)); rect.add(ctx.paint); } - let mut rect = PaintRect::new(layout_node.rect); + let mut rect = RoundedRectangle::new(layout_node.rect, self.props.radius); rect.color = self.props.overlay_color; rect.add(ctx.paint); diff --git a/crates/yakui/examples/blur.rs b/crates/yakui/examples/blur.rs index 78d68c86..57f6014f 100644 --- a/crates/yakui/examples/blur.rs +++ b/crates/yakui/examples/blur.rs @@ -23,12 +23,14 @@ pub fn run(state: &mut ExampleState) { let mut col = List::column(); col.cross_axis_alignment = CrossAxisAlignment::Stretch; col.show(|| { - CutOut::new(state.monkey_blurred, Color::hex(0x5cc9ff).with_alpha(0.25)) - .show_children(|| { - Pad::all(16.0).show(|| { - text(48.0, "Blur Demo"); - }); + let mut cut_out = + CutOut::new(state.monkey_blurred, Color::hex(0x5cc9ff).with_alpha(0.25)); + cut_out.radius = 25.0; + cut_out.show_children(|| { + Pad::all(16.0).show(|| { + text(48.0, "Blur Demo"); }); + }); CutOut::new(state.monkey_blurred, Color::hex(0x444444).with_alpha(0.1)) .show_children(|| {