use crate::core::{Bounds, Corners, Edges, Pixels, Point, Rgba, Size};
use crate::gpu::text::TextPlacement;
use crate::gpu::{Blur, ImageSource, MonochromeSprite, Quad, Shadow, ShapedText, Underline};
use crate::view::context::ElementContext;
use crate::view::style::Style;
use crate::view::theme::Token;
impl ElementContext<'_> {
pub fn paint_box(&mut self, bounds: Bounds, style: &Style) {
let mask = self.mask();
let opacity = style.opacity.unwrap_or(1.0);
let radius = style.radius.unwrap_or_default().min(bounds.size.width / 2.0).min(bounds.size.height / 2.0);
let corners = corner_radii(style, bounds.size);
for layer in style.shadow.iter().flat_map(|shadows| shadows.layers().iter().rev()) {
let color = layer.color.map_or(self.theme.shadow, |spec| self.theme.resolve(spec));
let color = color.with_alpha(color.a * layer.strength * opacity);
let spread = Pixels(layer.spread);
let grown = Size::new(
Pixels((bounds.size.width.0 + spread.0 * 2.0).max(0.0)),
Pixels((bounds.size.height.0 + spread.0 * 2.0).max(0.0)),
);
let origin = bounds.origin + layer.offset - Point::new(spread, spread);
let corners = Corners::all(Pixels((radius.0 + spread.0).max(0.0)));
self.frame.scene.insert_shadow(Shadow::new(Bounds::new(origin, grown), mask, color, corners, layer.blur));
}
self.paint_ring(bounds, style, corners);
let background = style.background.map(|spec| self.theme.resolve(spec));
let border = border_edges(style);
let has_border = [border.top, border.right, border.bottom, border.left].iter().any(|side| *side > Pixels::ZERO);
if background.is_none() && !has_border {
return;
}
let fill = background.unwrap_or(Rgba::TRANSPARENT);
let mut quad = Quad::new(bounds, mask, fade(fill, opacity)).corners(corners);
if let (Some((angle, end)), Some(_)) = (style.gradient, background) {
quad = quad.gradient(angle, fade(self.theme.resolve(end), opacity));
}
if has_border {
let border_color =
style.border_color.map_or(self.theme.color(Token::Border), |spec| self.theme.resolve(spec));
quad = quad.border(border, fade(border_color, opacity));
}
self.frame.scene.insert_quad(quad);
}
pub fn paint_blur(&mut self, bounds: Bounds, style: &Style, sigma: f32) {
if sigma <= 0.0 {
return;
}
let corners = corner_radii(style, bounds.size);
let transform = self.frame.scene.transform();
let [x, y, width, height] =
transform.bounds_of([bounds.origin.x.0, bounds.origin.y.0, bounds.size.width.0, bounds.size.height.0]);
let (scale, mask) = (transform.scale_factor(), self.mask());
let corners = Corners {
top_left: corners.top_left * scale,
top_right: corners.top_right * scale,
bottom_right: corners.bottom_right * scale,
bottom_left: corners.bottom_left * scale,
};
let area = Bounds::new(Point::new(Pixels(x), Pixels(y)), Size::new(Pixels(width), Pixels(height)));
self.frame.scene.insert_blur(Blur::new(area, mask, corners, sigma * scale));
}
fn paint_ring(&mut self, bounds: Bounds, style: &Style, corners: Corners) {
let Some(extra) = style.extra.as_deref() else { return };
let Some(ring) = extra.ring.filter(|width| *width > Pixels::ZERO) else { return };
let color =
extra.ring_color.map_or(self.theme.color(Token::Accent).with_alpha(0.5), |spec| self.theme.resolve(spec));
let grow = |radius: Pixels| radius + ring;
let outer = Corners {
top_left: grow(corners.top_left),
top_right: grow(corners.top_right),
bottom_right: grow(corners.bottom_right),
bottom_left: grow(corners.bottom_left),
};
let size = Size::new(bounds.size.width + ring * 2.0, bounds.size.height + ring * 2.0);
let quad = Quad::new(Bounds::new(bounds.origin - Point::new(ring, ring), size), self.mask(), Rgba::TRANSPARENT)
.corners(outer)
.border(Edges::all(ring), fade(color, style.opacity.unwrap_or(1.0)));
self.frame.scene.insert_quad(quad);
}
pub fn fill_rect(&mut self, bounds: Bounds, color: Rgba) {
let quad = Quad::new(bounds, self.mask(), color);
self.frame.scene.insert_quad(quad);
}
pub fn paint_gradient_rect(&mut self, bounds: Bounds, angle: f32, from: Rgba, to: Rgba) {
let quad = Quad::new(bounds, self.mask(), from).gradient(angle, to);
self.frame.scene.insert_quad(quad);
}
pub fn paint_underline(&mut self, bounds: Bounds, color: Rgba, wavy: bool) {
let underline = Underline::new(bounds, self.mask(), color, 1.0, wavy);
self.frame.scene.insert_underline(underline);
}
pub fn paint_outline(&mut self, bounds: Bounds, color: Rgba, radius: Pixels) {
let quad = Quad::new(bounds, self.mask(), Rgba::TRANSPARENT)
.corners(Corners::all(radius))
.border(Edges::all(Pixels(1.0)), color);
self.frame.scene.insert_quad(quad);
}
pub fn paint_rounded_rect(&mut self, bounds: Bounds, color: Rgba, radius: Pixels) {
let quad = Quad::new(bounds, self.mask(), color).corners(Corners::all(radius));
self.frame.scene.insert_quad(quad);
}
pub fn paint_text(&mut self, shaped: &ShapedText, origin: Point, color: Rgba) {
let placement = TextPlacement { origin, color, clip: self.mask(), scale: self.scale };
let Some(target) = self.glyphs.as_mut() else {
return;
};
self.text.paint(shaped, &placement, target, &mut self.frame.scene);
}
pub fn measure(&self, shaped: &ShapedText) -> Size {
shaped.size
}
pub fn paint_icon(&mut self, name: &'static str, svg: &'static [u8], bounds: Bounds, color: Rgba) {
let size_px = (bounds.size.width.0.max(bounds.size.height.0) * self.scale).round() as u32;
let mask = self.mask();
let Some(target) = self.glyphs.as_mut() else { return };
if let Some(tile) = self.sprites.icon(name, svg, size_px, target) {
self.frame.scene.insert_monochrome(MonochromeSprite::new(bounds, mask, tile.uv, color));
}
}
pub fn forget_image(&mut self, id: u64) {
let Some(target) = self.glyphs.as_mut() else { return };
self.sprites.forget_image(id, target);
}
pub fn paint_image(&mut self, source: &ImageSource, bounds: Bounds) {
let mask = self.mask();
let visible = mask.intersect(&bounds);
if visible.size.width <= Pixels::ZERO || visible.size.height <= Pixels::ZERO {
return;
}
let Some(target) = self.glyphs.as_mut() else { return };
if let Some(tile) = self.sprites.image(source, target) {
self.frame.scene.insert_polychrome(MonochromeSprite::new(
bounds,
mask,
tile.uv,
Rgba::new(1.0, 1.0, 1.0, 1.0),
));
}
}
}
fn corner_radii(style: &Style, size: Size) -> Corners {
let limit = (size.width / 2.0).min(size.height / 2.0);
let base = style.radius.unwrap_or_default();
let own = style.extra.as_deref().map(|extra| extra.corners).unwrap_or_default();
let pick = |corner: Option<Pixels>| corner.unwrap_or(base).min(limit);
Corners {
top_left: pick(own.top_left),
top_right: pick(own.top_right),
bottom_right: pick(own.bottom_right),
bottom_left: pick(own.bottom_left),
}
}
fn border_edges(style: &Style) -> Edges {
let all = style.border_width.unwrap_or_default();
let own = style.extra.as_deref().map(|extra| extra.border).unwrap_or_default();
Edges {
top: own.top.unwrap_or(all),
right: own.right.unwrap_or(all),
bottom: own.bottom.unwrap_or(all),
left: own.left.unwrap_or(all),
}
}
fn fade(color: Rgba, opacity: f32) -> Rgba {
color.with_alpha(color.a * opacity)
}