Skip to content

Commit

Permalink
allow cutout to be rounded (#140)
Browse files Browse the repository at this point in the history
  • Loading branch information
Uriopass authored Jan 25, 2024
1 parent 7295362 commit b5ae11d
Show file tree
Hide file tree
Showing 3 changed files with 49 additions and 24 deletions.
52 changes: 36 additions & 16 deletions crates/yakui-widgets/src/shapes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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] = [
Expand Down Expand Up @@ -167,6 +168,7 @@ pub struct RoundedRectangle {
pub rect: Rect,
pub radius: f32,
pub color: Color,
pub texture: Option<(TextureId, Rect)>,
}

impl RoundedRectangle {
Expand All @@ -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.
Expand All @@ -193,22 +191,43 @@ 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();

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);

Expand All @@ -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,
Expand All @@ -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);
}
}
9 changes: 6 additions & 3 deletions crates/yakui-widgets/src/widgets/cutout.rs
Original file line number Diff line number Diff line change
@@ -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};

Expand All @@ -18,6 +18,7 @@ pub struct CutOut {
pub image_color: Color,
pub overlay_color: Color,
pub min_size: Vec2,
pub radius: f32,
}

impl CutOut {
Expand All @@ -30,6 +31,7 @@ impl CutOut {
image_color: Color::WHITE,
overlay_color,
min_size: Vec2::ZERO,
radius: 0.0,
}
}

Expand Down Expand Up @@ -60,6 +62,7 @@ impl Widget for CutOutWidget {
image_color: Color::WHITE,
overlay_color: Color::CLEAR,
min_size: Vec2::ZERO,
radius: 0.0,
},
}
}
Expand Down Expand Up @@ -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);

Expand Down
12 changes: 7 additions & 5 deletions crates/yakui/examples/blur.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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(|| {
Expand Down

0 comments on commit b5ae11d

Please sign in to comment.