blob: 95a600c70cda767824ce7dd4e68551f536a8b4c2 [file]
// Copyright 2025 the Vello Authors
// SPDX-License-Identifier: Apache-2.0 OR MIT
//! Clip example showing deeply nested clipping.
#![expect(
clippy::cast_possible_truncation,
reason = "We temporarily ignore those because the casts\
only break in edge cases, and some of them are also only related to conversions from f64 to f32."
)]
use crate::{ExampleScene, RenderingContext};
use vello_common::color::palette::css::{
BLACK, BLUE, DARK_BLUE, DARK_GREEN, GREEN, REBECCA_PURPLE, RED,
};
use vello_common::kurbo::{Affine, BezPath, Circle, Point, Rect, Shape, Stroke};
use vello_common::peniko::Color;
/// Clip scene state
#[derive(Debug)]
pub struct ClipScene {
use_clip_path: bool,
num_circles: usize,
}
impl ExampleScene for ClipScene {
fn render(&mut self, ctx: &mut impl RenderingContext, root_transform: Affine) {
render(ctx, root_transform, self.use_clip_path, self.num_circles);
}
fn handle_key(&mut self, key: &str) -> bool {
match key {
"c" | "C" => {
self.toggle_clip();
true
}
"m" | "M" => {
self.add_circle();
true
}
_ => false,
}
}
}
impl ClipScene {
/// Create a new `ClipScene`
pub fn new() -> Self {
Self {
use_clip_path: false,
num_circles: 1,
}
}
/// Toggle using clip path
pub fn toggle_clip(&mut self) {
self.use_clip_path = !self.use_clip_path;
println!("Use clip path: {}", self.use_clip_path);
}
/// Add another circle to the scene
pub fn add_circle(&mut self) {
self.num_circles += 1;
println!("Number of circles: {}", self.num_circles);
}
}
impl Default for ClipScene {
fn default() -> Self {
Self::new()
}
}
fn draw_clipping_outline(ctx: &mut impl RenderingContext, path: &BezPath) {
let stroke = Stroke::new(1.0);
ctx.set_paint(DARK_BLUE);
ctx.set_stroke(stroke);
ctx.stroke_path(path);
}
/// Draws a deeply nested clip of circles.
pub fn render(
ctx: &mut impl RenderingContext,
root_transform: Affine,
use_clip_path: bool,
num_circles: usize,
) {
const INITIAL_RADIUS: f64 = 48.0;
const RADIUS_DECREMENT: f64 = 2.5;
const INNER_COUNT: usize = 10;
// `.ceil()` is not constant-evaluatable, so we have to do this at runtime.
let outer_count: usize =
(INITIAL_RADIUS / RADIUS_DECREMENT / INNER_COUNT as f64).ceil() as usize;
const COLORS: [Color; INNER_COUNT] = [
RED,
DARK_BLUE,
DARK_GREEN,
REBECCA_PURPLE,
BLACK,
BLUE,
GREEN,
RED,
DARK_BLUE,
DARK_GREEN,
];
const SPACING: f64 = 120.0;
const BASE_X: f64 = 50.0;
const BASE_Y: f64 = 50.0;
ctx.set_transform(root_transform);
// Draw multiple circles in a checkerboard pattern
for circle_idx in 0..num_circles {
// Calculate checkerboard position
// Create a grid pattern where circles are placed in a checkerboard layout
let row = circle_idx / 4;
let col = circle_idx % 4;
// Create checkerboard offset pattern
let offset_x = if (row + col) % 2 == 0 {
0.0
} else {
SPACING / 2.0
};
let x = BASE_X + col as f64 * SPACING + offset_x;
let y = BASE_Y + row as f64 * SPACING;
let center = Point::new(x, y);
let cover_rect = Rect::new(x - 50.0, y - 50.0, x + 50.0, y + 50.0);
let mut radius = INITIAL_RADIUS;
for _ in 0..outer_count {
for color in COLORS.iter() {
let clip_circle = Circle::new(center, radius).to_path(0.1);
draw_clipping_outline(ctx, &clip_circle);
if use_clip_path {
ctx.push_clip_path(&clip_circle);
} else {
ctx.push_clip_layer(&clip_circle);
}
ctx.set_paint(*color);
ctx.fill_rect(&cover_rect);
radius -= RADIUS_DECREMENT;
}
}
for _ in 0..outer_count {
for _ in COLORS.iter() {
if !use_clip_path {
ctx.pop_layer();
} else {
ctx.pop_clip_path();
}
}
}
}
}