← luna

Commit 25711e9

2
files
+50
added
-10
deleted
@@ -130,6 +130,8 @@ pub enum DrawCommand {
130 130 FillPolygon { points: Vec<Point>, color: Color },
131 131 /// Ogranicza kolejne polecenia do prostokąta (zastosowanie kumulacyjne).
132 132 PushClip { rect: Rect },
133 + /// Ogranicza kolejne polecenia do **zaokrąglonego** prostokąta (kumulacyjnie).
134 + PushClipRounded { rect: Rect, radius: f64 },
133 135 /// Zdejmuje ostatnie ograniczenie.
134 136 PopClip,
135 137 /// Tekst.
@@ -262,6 +264,13 @@ impl Scene {
262 264 self
263 265 }
264 266
267 + /// Ogranicza kolejne polecenia do zaokrąglonego prostokąta (np. narożniki okna).
268 + pub fn push_clip_rounded(&mut self, rect: Rect, radius: f64) -> &mut Self {
269 + self.commands
270 + .push(DrawCommand::PushClipRounded { rect, radius });
271 + self
272 + }
273 +
265 274 pub fn pop_clip(&mut self) -> &mut Self {
266 275 self.commands.push(DrawCommand::PopClip);
267 276 self
@@ -79,7 +79,7 @@ impl SoftwareRenderer {
79 79 pixmap.fill(ts_color(background));
80 80 }
81 81
82 - let mut clip_stack: Vec<Rect> = Vec::new();
82 + let mut clip_stack: Vec<(Rect, f64)> = Vec::new();
83 83 let mut mask: Option<Mask> = None;
84 84
85 85 for cmd in scene.commands() {
@@ -166,25 +166,38 @@ impl SoftwareRenderer {
166 166 }
167 167 }
168 168 DrawCommand::PushClip { rect } => {
169 - let effective = match clip_stack.last() {
170 - Some(current) => current.intersection(rect).unwrap_or(Rect::ZERO),
171 - None => *rect,
169 + let (effective, radius) = match clip_stack.last() {
170 + Some((current, _)) => {
171 + (current.intersection(rect).unwrap_or(Rect::ZERO), 0.0)
172 + }
173 + None => (*rect, 0.0),
174 + };
175 + clip_stack.push((effective, radius));
176 + mask = build_mask(pixmap, effective, radius, transform);
177 + }
178 + DrawCommand::PushClipRounded { rect, radius } => {
179 + let (effective, effective_radius) = match clip_stack.last() {
180 + Some((current, current_radius)) => (
181 + current.intersection(rect).unwrap_or(Rect::ZERO),
182 + radius.min(*current_radius),
183 + ),
184 + None => (*rect, *radius),
172 185 };
173 - clip_stack.push(effective);
174 - mask = build_mask(pixmap, effective, transform);
186 + clip_stack.push((effective, effective_radius));
187 + mask = build_mask(pixmap, effective, effective_radius, transform);
175 188 }
176 189 DrawCommand::PopClip => {
177 190 clip_stack.pop();
178 191 mask = clip_stack
179 192 .last()
180 193 .copied()
181 - .and_then(|r| build_mask(pixmap, r, transform));
194 + .and_then(|(r, rad)| build_mask(pixmap, r, rad, transform));
182 195 }
183 196 DrawCommand::Text(run) => {
184 197 let (w, h) = (pixmap.width() as usize, pixmap.height() as usize);
185 198 // Tekst nie korzysta z maski kształtów — obcinamy wprost piksele.
186 199 let k = scale.factor();
187 - let clip = clip_stack.last().map(|r| crate::text::ClipRect {
200 + let clip = clip_stack.last().map(|(r, _)| crate::text::ClipRect {
188 201 x0: (r.x() * k).floor() as i64,
189 202 y0: (r.y() * k).floor() as i64,
190 203 x1: (r.right() * k).ceil() as i64,
@@ -382,9 +395,9 @@ fn stroke(
382 395 pixmap.stroke_path(path, &paint(color), &stroke, transform, mask);
383 396 }
384 397
385 -fn build_mask(pixmap: &Pixmap, rect: Rect, transform: Transform) -> Option<Mask> {
398 +fn build_mask(pixmap: &Pixmap, rect: Rect, radius: f64, transform: Transform) -> Option<Mask> {
386 399 let mut mask = Mask::new(pixmap.width(), pixmap.height())?;
387 - if let Some(path) = rect_path(rect, 0.0) {
400 + if let Some(path) = rect_path(rect, radius) {
388 401 mask.fill_path(&path, FillRule::Winding, true, transform);
389 402 }
390 403 Some(mask)
@@ -570,6 +583,24 @@ mod tests {
570 583 );
571 584 }
572 585
586 + #[test]
587 + fn rounded_clip_cuts_corners() {
588 + let mut r = SoftwareRenderer::new(40, 40);
589 + let mut scene = Scene::new();
590 + scene
591 + .push_clip_rounded(Rect::from_xywh(0.0, 0.0, 40.0, 40.0), 12.0)
592 + .fill_rect(
593 + Rect::from_xywh(0.0, 0.0, 40.0, 40.0),
594 + Color::from_rgba8(255, 0, 0, 255),
595 + )
596 + .pop_clip();
597 + r.draw(&scene, 1.0, Color::WHITE);
598 +
599 + // Narożnik poza promieniem pozostaje tłem, środek jest wypełniony.
600 + assert_eq!(pixel(&r, 1, 1), [255, 255, 255, 255]);
601 + assert_eq!(pixel(&r, 20, 20), [255, 0, 0, 255]);
602 + }
603 +
573 604 #[test]
574 605 fn clip_limits_drawing() {
575 606 let mut r = SoftwareRenderer::new(40, 40);