use crate::renderer::Renderer;
use crate::style::Style;
use crate::widget::{Color, Rect};
fn isqrt(n: u32) -> u32 {
if n == 0 {
return 0;
}
let mut x = n;
let mut y = x.div_ceil(2);
while y < x {
x = y;
y = (x + n / x) / 2;
}
x
}
fn arc_dx(r: i32, dy: i32) -> (i32, u8) {
let r4 = r as u32 * 4;
let dy4 = dy as u32 * 4 + 2; let sq = r4 * r4;
let dysq = dy4 * dy4;
if dysq >= sq {
return (0, 0);
}
let dx4 = isqrt(sq - dysq);
let dx_int = (dx4 / 4) as i32;
let frac = (dx4 % 4) as u8 * 64; (dx_int, frac)
}
pub fn fill_rounded_rect(renderer: &mut dyn Renderer, rect: Rect, color: Color, radius: u8) {
let r = radius as i32;
if r == 0 {
renderer.fill_rect(rect, color);
return;
}
let r = r.min(rect.width / 2).min(rect.height / 2);
if r <= 0 {
renderer.fill_rect(rect, color);
return;
}
if rect.height - 2 * r > 0 {
renderer.fill_rect(
Rect {
x: rect.x,
y: rect.y + r,
width: rect.width,
height: rect.height - 2 * r,
},
color,
);
}
if rect.width - 2 * r > 0 {
renderer.fill_rect(
Rect {
x: rect.x + r,
y: rect.y,
width: rect.width - 2 * r,
height: r,
},
color,
);
renderer.fill_rect(
Rect {
x: rect.x + r,
y: rect.y + rect.height - r,
width: rect.width - 2 * r,
height: r,
},
color,
);
}
let base_alpha = color.3 as u16;
for dy in 0..r {
let (dx_int, frac) = arc_dx(r, dy);
if dx_int > 0 {
renderer.fill_rect(
Rect {
x: rect.x + r - dx_int,
y: rect.y + dy,
width: dx_int,
height: 1,
},
color,
);
renderer.fill_rect(
Rect {
x: rect.x + rect.width - r,
y: rect.y + dy,
width: dx_int,
height: 1,
},
color,
);
renderer.fill_rect(
Rect {
x: rect.x + r - dx_int,
y: rect.y + rect.height - 1 - dy,
width: dx_int,
height: 1,
},
color,
);
renderer.fill_rect(
Rect {
x: rect.x + rect.width - r,
y: rect.y + rect.height - 1 - dy,
width: dx_int,
height: 1,
},
color,
);
}
if frac > 0 {
let aa_alpha = ((frac as u16 * base_alpha) / 255) as u8;
let aa = Color(color.0, color.1, color.2, aa_alpha);
renderer.blend_rect(
Rect {
x: rect.x + r - dx_int - 1,
y: rect.y + dy,
width: 1,
height: 1,
},
aa,
);
renderer.blend_rect(
Rect {
x: rect.x + rect.width - r + dx_int,
y: rect.y + dy,
width: 1,
height: 1,
},
aa,
);
renderer.blend_rect(
Rect {
x: rect.x + r - dx_int - 1,
y: rect.y + rect.height - 1 - dy,
width: 1,
height: 1,
},
aa,
);
renderer.blend_rect(
Rect {
x: rect.x + rect.width - r + dx_int,
y: rect.y + rect.height - 1 - dy,
width: 1,
height: 1,
},
aa,
);
}
}
}
pub fn draw_rounded_border(
renderer: &mut dyn Renderer,
rect: Rect,
color: Color,
border_width: u8,
radius: u8,
) {
let bw = border_width as i32;
if bw == 0 {
return;
}
let r = radius as i32;
if r == 0 {
draw_border_straight(renderer, rect, color, border_width);
return;
}
let rout = r.min(rect.width / 2).min(rect.height / 2);
if rout <= 0 {
draw_border_straight(renderer, rect, color, border_width);
return;
}
let rin = (rout - bw).max(0);
let base_alpha = color.3 as u16;
for dy in 0..rout {
let (out_dx, out_frac) = arc_dx(rout, dy);
let (in_dx, in_frac) = if rin > 0 {
let (d, f) = arc_dx(rin, dy);
(d, f)
} else {
(0i32, 0u8)
};
let ring_w = out_dx - in_dx;
if ring_w > 0 {
renderer.fill_rect(
Rect {
x: rect.x + rout - out_dx,
y: rect.y + dy,
width: ring_w,
height: 1,
},
color,
);
renderer.fill_rect(
Rect {
x: rect.x + rect.width - rout + in_dx,
y: rect.y + dy,
width: ring_w,
height: 1,
},
color,
);
renderer.fill_rect(
Rect {
x: rect.x + rout - out_dx,
y: rect.y + rect.height - 1 - dy,
width: ring_w,
height: 1,
},
color,
);
renderer.fill_rect(
Rect {
x: rect.x + rect.width - rout + in_dx,
y: rect.y + rect.height - 1 - dy,
width: ring_w,
height: 1,
},
color,
);
}
if out_frac > 0 {
let aa_alpha = ((out_frac as u16 * base_alpha) / 255) as u8;
let aa = Color(color.0, color.1, color.2, aa_alpha);
renderer.blend_rect(
Rect {
x: rect.x + rout - out_dx - 1,
y: rect.y + dy,
width: 1,
height: 1,
},
aa,
);
renderer.blend_rect(
Rect {
x: rect.x + rect.width - rout + out_dx,
y: rect.y + dy,
width: 1,
height: 1,
},
aa,
);
renderer.blend_rect(
Rect {
x: rect.x + rout - out_dx - 1,
y: rect.y + rect.height - 1 - dy,
width: 1,
height: 1,
},
aa,
);
renderer.blend_rect(
Rect {
x: rect.x + rect.width - rout + out_dx,
y: rect.y + rect.height - 1 - dy,
width: 1,
height: 1,
},
aa,
);
}
if in_dx > 0 && in_frac > 0 {
let aa_alpha = (((255 - in_frac as u16) * base_alpha) / 255) as u8;
let aa = Color(color.0, color.1, color.2, aa_alpha);
renderer.blend_rect(
Rect {
x: rect.x + rout - in_dx,
y: rect.y + dy,
width: 1,
height: 1,
},
aa,
);
renderer.blend_rect(
Rect {
x: rect.x + rect.width - rout + in_dx - 1,
y: rect.y + dy,
width: 1,
height: 1,
},
aa,
);
renderer.blend_rect(
Rect {
x: rect.x + rout - in_dx,
y: rect.y + rect.height - 1 - dy,
width: 1,
height: 1,
},
aa,
);
renderer.blend_rect(
Rect {
x: rect.x + rect.width - rout + in_dx - 1,
y: rect.y + rect.height - 1 - dy,
width: 1,
height: 1,
},
aa,
);
}
}
let straight_h = rect.height - 2 * rout;
if straight_h > 0 {
renderer.fill_rect(
Rect {
x: rect.x,
y: rect.y + rout,
width: bw,
height: straight_h,
},
color,
);
renderer.fill_rect(
Rect {
x: rect.x + rect.width - bw,
y: rect.y + rout,
width: bw,
height: straight_h,
},
color,
);
}
let straight_w = rect.width - 2 * rout;
if straight_w > 0 {
renderer.fill_rect(
Rect {
x: rect.x + rout,
y: rect.y,
width: straight_w,
height: bw,
},
color,
);
renderer.fill_rect(
Rect {
x: rect.x + rout,
y: rect.y + rect.height - bw,
width: straight_w,
height: bw,
},
color,
);
}
}
pub fn draw_border_straight(renderer: &mut dyn Renderer, rect: Rect, color: Color, width: u8) {
let w = width as i32;
if w == 0 {
return;
}
renderer.fill_rect(
Rect {
x: rect.x,
y: rect.y,
width: rect.width,
height: w,
},
color,
);
renderer.fill_rect(
Rect {
x: rect.x,
y: rect.y + rect.height - w,
width: rect.width,
height: w,
},
color,
);
renderer.fill_rect(
Rect {
x: rect.x,
y: rect.y + w,
width: w,
height: rect.height - 2 * w,
},
color,
);
renderer.fill_rect(
Rect {
x: rect.x + rect.width - w,
y: rect.y + w,
width: w,
height: rect.height - 2 * w,
},
color,
);
}
pub fn draw_widget_bg(renderer: &mut dyn Renderer, rect: Rect, style: &Style) {
let bg = style.bg_color.with_alpha(style.alpha);
if style.radius > 0 {
fill_rounded_rect(renderer, rect, bg, style.radius);
} else {
renderer.fill_rect(rect, bg);
}
if style.border_width > 0 {
let border = style.border_color.with_alpha(style.alpha);
draw_rounded_border(renderer, rect, border, style.border_width, style.radius);
}
}
#[cfg(test)]
mod tests {
use super::*;
struct RecordRenderer {
fill_rects: alloc::vec::Vec<(Rect, Color)>,
blend_rects: alloc::vec::Vec<(Rect, Color)>,
}
impl RecordRenderer {
fn new() -> Self {
Self {
fill_rects: alloc::vec::Vec::new(),
blend_rects: alloc::vec::Vec::new(),
}
}
}
impl Renderer for RecordRenderer {
fn fill_rect(&mut self, rect: Rect, color: Color) {
self.fill_rects.push((rect, color));
}
fn blend_rect(&mut self, rect: Rect, color: Color) {
self.blend_rects.push((rect, color));
}
fn draw_text(&mut self, _pos: (i32, i32), _text: &str, _color: Color) {}
}
struct CountRenderer {
fills: u32,
blends: u32,
}
impl Renderer for CountRenderer {
fn fill_rect(&mut self, _rect: Rect, _color: Color) {
self.fills += 1;
}
fn blend_rect(&mut self, _rect: Rect, _color: Color) {
self.blends += 1;
}
fn draw_text(&mut self, _pos: (i32, i32), _text: &str, _color: Color) {}
}
#[test]
fn zero_radius_single_fill() {
let mut r = CountRenderer {
fills: 0,
blends: 0,
};
let rect = Rect {
x: 0,
y: 0,
width: 100,
height: 50,
};
fill_rounded_rect(&mut r, rect, Color(0, 0, 0, 255), 0);
assert_eq!(r.fills, 1);
assert_eq!(r.blends, 0);
}
#[test]
fn radius_clamped_for_pill_shape() {
let mut r = CountRenderer {
fills: 0,
blends: 0,
};
let rect = Rect {
x: 0,
y: 0,
width: 40,
height: 20,
};
fill_rounded_rect(&mut r, rect, Color(0, 0, 0, 255), 30);
assert!(r.fills > 1, "expected corners, got {} fills", r.fills);
}
#[test]
fn aa_fringe_produces_blend_calls() {
let mut r = CountRenderer {
fills: 0,
blends: 0,
};
let rect = Rect {
x: 0,
y: 0,
width: 100,
height: 100,
};
fill_rounded_rect(&mut r, rect, Color(255, 0, 0, 255), 10);
assert!(r.blends > 0, "expected AA blend calls, got 0");
}
#[test]
fn rounded_border_produces_ring() {
let mut r = RecordRenderer::new();
let rect = Rect {
x: 0,
y: 0,
width: 60,
height: 60,
};
draw_rounded_border(&mut r, rect, Color(0, 0, 0, 255), 2, 8);
assert!(!r.fill_rects.is_empty(), "expected border fills");
}
#[test]
fn straight_border_four_strips() {
let mut r = CountRenderer {
fills: 0,
blends: 0,
};
let rect = Rect {
x: 0,
y: 0,
width: 100,
height: 50,
};
draw_border_straight(&mut r, rect, Color(0, 0, 0, 255), 2);
assert_eq!(r.fills, 4);
}
#[test]
fn draw_widget_bg_uses_radius() {
let mut r = CountRenderer {
fills: 0,
blends: 0,
};
let rect = Rect {
x: 0,
y: 0,
width: 80,
height: 40,
};
let style = Style {
bg_color: Color(100, 100, 100, 255),
border_color: Color(0, 0, 0, 255),
border_width: 1,
alpha: 255,
radius: 6,
};
draw_widget_bg(&mut r, rect, &style);
assert!(r.fills > 1);
}
}